Using Initializer Lists with Variadic Templates

c++, c++11, initializer-list, type-deduction, variadic-templates

Solution

You should have provided the signature of your member function `fun`. Let's assume that it has the following signature:

class YourClassObj
{
public:
    template<typename ...Args>
    SomeType fun(Args ...args) {...}
    ...
};

Then the following call:

fun(obj, {1, 2, 3});

with a forwarding function, causes actually a non-deduced context related to failing to deduce type for `{1,2,3}`. As per the Standard:

14.8.2.1 Deducing template arguments from a function call [temp.deduct.call]

Template argument deduction is done by comparing each function template parameter type (call it `P`) with the type of the corresponding argument of the call (call it `A`) as described below. If `P` is a dependent type, removing references and cv-qualifiers from `P` gives `std::initializer_list<P'>` or `P'[N]` for some `P'` and `N` and the argument is a non-empty initializer list (8.5.4), then deduction is performed instead for each element of the initializer list, taking `P'` as a function template parameter type and the initializer element as its argument, and in the `P'[N]` case, if `N` is a non-type template parameter, `N` is deduced from the length of the initializer list. Otherwise, an initializer list argument causes the parameter to be considered a non-deduced context (14.8.2.5).

Examples from the standard:

   template<class T> void f(std::initializer_list<T>);
   f({1,2,3}); // T deduced to int
   template<class T> void g(T);
   g({1,2,3}); // error: no argument deduced for T

`std::forward` essentially does template type deduction from function call. Scott Meyers describes in his Effective Modern C++ book this situation as one of the perfect forwarding failure cases. As he said in the book, one simple workaround is to use `auto`:

auto __il = {1,2,3};
fun(obj, __il);

should compile fine, since `auto` deduces `{1,2,3}` to be a `std::initializer_list<int>`. You can read the whole chapter for more detailed explanation.

And by the way, the other approach with a function `il` returning a `std::initializer_list<T>` should also compile, if the original `fun` signature is actually the same as what I assumed (tested with gcc-4.9.1).

Problem

I have the following code: ``` #define RETURNS(...) -> decltype((__VA_ARGS__)) { return (__VA_ARGS__); } template <typename This, typename... Args> auto fun(This &&this_, Args&&... args) RETURNS(this_.fun(std::forward<Args>(args)...)) ``` For better or worse, this allows me to use fun(o, args...) and o.f(args...) interchangeably. The difficulty in using this comes when using an initializer list as an argument. E.g. ``` fun(obj, {1, 2, 3}); // Where obj.fun eventually takes a std::vector<int>. ``` This fails due to a substitution error, so Clang says. Note, `obj.fun({1, 2, 3});` works. As I understand it from other questions, this is because initializer lists don't always play nicely with template argument deduction. The closest I have to my desired syntax is by making the initializer list more explicit. To avoid verbosity, I have the following: ``` template <typename T> std::initializer_list<T> il(std::initializer_list<T> &&li) { return li; } fun(obj, il({1, 2, 3})); ``` Is there a way of getting my desired syntax or closer to it? Clang's error report in my test program is: ``` subst.cpp:16:5: error: no matching function for call to 'fun' fun(x, {1, 2, 3}); ^~~ subst.cpp:6:6: note: candidate template ignored: substitution failure [with This = X &, Args = <>]: too few arguments to function call, single argument 'v' was not specified auto fun(This &&this_, Args&&... args) RETURNS(this_.fun(std::forward<Args>(args)...)) ^ ~ ```

Original source

Related problems