4

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)...))
     ^                                                                              ~
8
  • A substitution error? It should be a deduction failure. {1,2,3} is not an expression, and it has no type that could be deduced. Commented Feb 5, 2014 at 11:24
  • @dyp I have added the error report from Clang. I think we're in violent agreement, although I may be putting the cart before the horse. Args does not get deduced (Args = <>), thus results in a substitution failure. Commented Feb 5, 2014 at 12:54
  • That sound like a quite unhelpful error message. Maybe you should report that to the clang developers. Commented Feb 5, 2014 at 13:08
  • You can also let il take a variable number of arguments, to allow a syntax like fun(obj, il(1,2,3)). Alternatively, an ugly fun<some_type, std::initializer_list<int>>(obj, {1,2,3}) is also possible (but not recommended). Commented Feb 5, 2014 at 13:11
  • @dyp I think it would take some concerted templateze to expand il(a, b, c, ...) to std::initializer_list<A>{a, b, c, ...}. Sounds like a challenge. Commented Feb 5, 2014 at 13:30

1 Answer 1

2

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).

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.