Is Substitution Performed on a Variadic Parameter Pack Type If the Pack Is Empty

Is substitution performed on a variadic parameter pack type if the pack is empty?

I believe I have found the relevant piece of standardese. §14.8.2p7 says:

The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations.

Since EnableIf<dependent_true_type<T>> is used in a template parameter declaration, substitution should occur and this is a bug in clang.

Dependent non-type parameter packs: what does the standard say?

The code is ill-formed, no diagnostic is required.

If std::is_signed_v<T>, then std::enable_if_t<std::is_signed_v<T>> denotes the type void. Otherwise, std::enable_if_t<std::is_signed_v<T>> does not denote a valid type. Therefore, every valid specialization of myAbs requires an empty template parameter pack.

Per [meta.rqmts]/4, the program has undefined behavior if std::enable_if is specialized. Therefore, the aforementioned behavior cannot be changed.

In my opinion IsSigned< T >... is a dependent template parameter,
therefore it can not be checked against §17.7 (8.3) in template
definition time. IsSigned< T > could be for example void for one
subset of Ts, int for another subset or substitution failure. For
the void subset it is true, that the empty template parameter pack
would be the only valid specialization, but the int subset could
have many valid specializations. It depends on the actual T
argument.

The compiler cannot check it, in the same way it cannot, say, solve an arbitrary equation for you. NDR (no diagnostic required) is made exactly for such cases — the program is ill-formed and would require a diagnostic if the compiler is actually capable of detecting that. NDR permits the compiler not to check it.

When N is zero, the instantiation of the expansion produces an empty
list. Such an instantiation does not alter the syntactic
interpretation of the enclosing construct.

The rule we are talking about is a semantic rule, not a syntactic rule, because syntactic rules are in [gram].


So what is the rationale for the NDR rules? In general, they address problems that are not reproducible among implementation strategies. For example, they may cause the code to misbehave in some implementation strategies, but do not cause any problems (and cannot be easily) in others.


Also, note that the standard talks in terms of program with terms like "ill-formed". Therefore, it is not always plausible to talk about the well-formed ness of an isolated code snippet. In this case, std::enable_if is required not to be specialized, but the situation may get more complicated otherwise.

incorrect deduction of template parameter pack

Template errors are hard to get right. It's just a quality of implementation. Clang for instances gives

main.cpp:2:6: note: candidate template ignored: substitution failure 
[with dim = 2, N = 3, P = true, C = false]: deduced incomplete pack <int, int, (no value)>
for template parameter 'ParametersType'

which is easier to understand. And yes, unless using auto, {stuff} has no type.

Template parameter pack deduction when not passed as last parameter

We can block deduction:

template<typename... Args>
void testfunc(const block_deduction<std::function<void (float, Args..., char)>>& func)

with

template<class T>
struct tag_t{using type=T;};

template<class T>
using block_deduction=typename tag_t<T>::type;

and now Args... is in a non-deduced context.

You can do fancier things with SFINAE and omitting char then testing that char is at the end of the Args..., but that seems overkill.


I will bet dollars to donuts that when gcc and clang disagree with MSVC, MSVC isn't right. But I have not standard delved to confirm that.

C++ Variadic template empty pack

You don't need any specializations of get, just

template<typename... An>
tuple<An...> get()
{
return tuple_cat(getSingle<An>()...);
}

Check if parameter pack contains a type

No, you have to use (partial) specialization with variadic templates to do compile-time computations like this:

#include <type_traits>

template < typename Tp, typename... List >
struct contains : std::true_type {};

template < typename Tp, typename Head, typename... Rest >
struct contains<Tp, Head, Rest...>
: std::conditional< std::is_same<Tp, Head>::value,
std::true_type,
contains<Tp, Rest...>
>::type {};

template < typename Tp >
struct contains<Tp> : std::false_type {};

There is only one other intrinsic operation for variadic templates and that is the special form of the sizeof operator which computes the length of the parameter list e.g.:

template < typename... Types >
struct typelist_len
{
const static size_t value = sizeof...(Types);
};

Where are you getting "it has serious compilation-time overhead" with boost mpl from? I hope you are not just making assumptions here. Boost mpl uses techniques such as lazy template instantiation to try and reduce compile-times instead of exploding like naive template meta-programming does.

Clang fails to expand parameter pack in std::function instantiation

TL;DR: it's a clang bug, but there's also a bug in the standard.

First be aware that in C++, templates are handled in two steps:

  1. Constructs that are not dependent on the template parameters are built when the enclosing template is defined.
  2. Constructs that are dependent are built when the enclosing template is instantiated.

Now, it seems that clang treats std::function< void(Int<Ts>...) > as a non-dependent type, with the following reasoning:

  1. Int<Ts> is a non-dependent type (correct).
  2. Thus, pack expansion containing Int<Ts> (i.e. Int<Ts>...) is also a non-dependent "type" (?).
  3. Because all components of void(Int<Ts>...) are non-dependent, it is a non-dependent type (obviously incorrect).
  4. Because the name std::function is non-dependent and the template argument void(Int<Ts>...) is a non-dependent non-pack-expansion type, std::function< void(Int<Ts>...) > is a non-dependent type.

(Note that the "non-pack-expansion" check makes it different from the Tuple case.)

Therefore, when Foo is defined, the type name Function is treated as naming a non-dependent type, and is immediately built, and pack expansion (which happens during instantiation) is not accounted. Consequently, all uses of Function is replaced with the "desugared" type std::function< void(int) >.

Furthermore, clang has the notion of instantiation-dependent, which means the construct is not dependent, but it still somehow involves the template parameters (e.g. the construct is only valid for some parameters). std::function< void(Int<Ts>...) > is treated as a instantiation-dependent type, so when the template is instantiated, clang still performs substitution for using Function = std::function< void(Int<Ts>...) >. As a result, Function gets the correct type, but this does not propagate to uses of Function in the definition of Foo.


Now heres the bug in the standard.

Whether a type is dependent is defined in [temp.dep.type]:

A type is dependent if it is

  • a template parameter,
  • a member of an unknown specialization,
  • a nested class or enumeration that is a dependent member of the current instantiation,
  • a cv-qualified type where the cv-unqualified type is dependent,
  • a compound type constructed from any dependent type,
  • an array type whose element type is dependent or whose bound (if any) is value-dependent,
  • a function type whose exception specification is value-dependent,
  • denoted by a simple-template-id in which either the template name is a template parameter or any of the template arguments is a dependent
    type or an expression that is type-dependent or value-dependent or is
    a pack expansion [ Note: This includes an injected-class-name of a
    class template used without a template-argument-list. — end note ] ,
    or
  • denoted by decltype(expression), where expression is type-dependent.

Note that it doesn't say that a function type whose parameter list contains pack expansions is a dependent type, only that "a compound type constructed from any dependent type" and "a function type whose exception specification is value-dependent" are dependent. Neither is helpful here.

How can a template parameter pack have both explicit and deduced arguments?

It's [temp.arg.explicit]/8:

Template argument deduction can extend the sequence of template arguments corresponding to a template parameter pack, even when the sequence contains explicitly specified template arguments. [ Example:

template<class ... Types> void f(Types ... values);

void g() {
f<int*, float*>(0, 0, 0); // Types is deduced to the sequence int*, float*, int
}

— end example ]



Related Topics



Leave a reply



Submit