Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variadic function template with pack expansion not in last parameter

Tags:

I am wondering why the following code doesn't compile:

struct S
{
    template <typename... T>
    S(T..., int);
};

S c{0, 0};

This code fails to compile with both clang and GCC 4.8. Here is the error with clang:

test.cpp:7:3: error: no matching constructor for initialization of 'S'
S c{0, 0};
  ^~~~~~~
test.cpp:4:5: note: candidate constructor not viable: requires 1 argument, but 2 were provided
    S(T..., int);
    ^

It seems to me that this should work, and T should be deduced to be a pack of length 1.

If the standards forbids doing things like this, does anyone know why?

like image 558
HighCommander4 Avatar asked Feb 08 '13 09:02

HighCommander4


People also ask

How do you expand a parameter pack?

Parameter packs can only be expanded in a strictly-defined list of contexts, and operator , is not one of them. In other words, it's not possible to use pack expansion to generate an expression consisting of a series of subexpressions delimited by operator , .

Which of the following are valid reasons for using Variadic templates in C++?

Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration. However, variadic templates help to overcome this issue.

What is parameter pack in C++?

Parameter packs (C++11) A parameter pack can be a type of parameter for templates. Unlike previous parameters, which can only bind to a single argument, a parameter pack can pack multiple parameters into a single parameter by placing an ellipsis to the left of the parameter name.

What is Variadic function in C?

Variadic functions are functions that can take a variable number of arguments. In C programming, a variadic function adds flexibility to the program. It takes one fixed argument and then any number of arguments can be passed.


2 Answers

Because when a function parameter pack is not the last parameter, then the template parameter pack cannot be deduced from it and it will be ignored by template argument deduction.

So the two arguments 0, 0 are compared against , int, yielding a mismatch.

Deduction rules like this need to cover many special cases (like what happens when two parameter packs appear next to each other). Since parameter packs are a new feature in C++11, the authors of the respective proposal drafted the rules conservatively.

Note that a trailing template parameter pack will be empty if it is not otherwise deduced. So when you call the constructor with one argument, things will work (notice the difference of template parameter pack and function parameter pack here. The former is trailing, the latter is not).

like image 114
Johannes Schaub - litb Avatar answered Sep 22 '22 22:09

Johannes Schaub - litb


So, there should be a workaround. Something along these lines:

namespace v1 {
  // Extract the last type in a parameter pack.
  // 0, the empty pack has no last type (only called if 1 and 2+ don't match)
  template<typename... Ts>
  struct last_type {};

  // 2+ in pack, recurse:
  template<typename T0, typename T1, typename... Ts>
  struct last_type<T0, T1, Ts...>:last_type<T1, Ts...>{};

  // Length 1, last type is only type:
  template<typename T0>
  struct last_type<T0> {
    typedef T0 type;
  };
}
namespace v2 {
  template<class T> struct tag_t{using type=T;};
  template<class T> using type_t = typename T::type;
  template<class...Ts>
  using last = type_t< std::tuple_element_t< sizeof...(Ts)-1, std::tuple<tag_t<Ts>...> > >;
  template<class...Ts>
  struct last_type {
    using type=last<Ts...>;
  };
}
template<class...Ts>
using last_type=v2::late_type<Ts...>; // or v1   


struct S
{
    // We accept any number of arguments
    // So long as the type of the last argument is an int
    // probably needs some std::decay to work right (ie, to implicitly work out that
    // the last argument is an int, and not a const int& or whatever)
    template <typename... T, typename=typename std::enable_if<std::is_same<int, typename last_type<T...>::type>>::type>
    S(T...);

};

where we check that the last type of a parameter pack is an int, or that we where only passed an int.

like image 38
Yakk - Adam Nevraumont Avatar answered Sep 25 '22 22:09

Yakk - Adam Nevraumont