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)...))
     ^                                                                              ~
Kaz Dragon
  • 6,681
  • 2
  • 36
  • 45
  • A substitution error? It should be a [deduction failure](http://coliru.stacked-crooked.com/a/859f8b2fac7c2cd8). `{1,2,3}` is not an expression, and it has no type that could be deduced. – dyp Feb 05 '14 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. – Kaz Dragon Feb 05 '14 at 12:54
  • That sound like a quite unhelpful error message. Maybe you should report that to the clang developers. – dyp Feb 05 '14 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>(obj, {1,2,3})` is also possible (but not recommended). – dyp Feb 05 '14 at 13:11
  • @dyp I think it would take some concerted templateze to expand `il(a, b, c, ...)` to `std::initializer_list{a, b, c, ...}`. Sounds like a challenge. – Kaz Dragon Feb 05 '14 at 13:30
  • Hmmm sorry my suggestion doesn't work, see http://stackoverflow.com/q/15286450/420683 Returning an `std::initializer_list` does not extend the lifetime of the underlying array; it might even be impossible to correctly return it from a function :( – dyp Feb 05 '14 at 13:47
  • @dyp I have filed http://llvm.org/bugs/show_bug.cgi?id=18740 , with a bit of uselessness tacked on due to a sudden bout of error message blindness. – Kaz Dragon Feb 05 '14 at 13:49
  • Of interest: this issue actually turns up on slide 13 of Scott Meyers's talk, "The Last Thing That D Needs" at D-Conf: http://www.ustream.tv/recorded/47947981/theater?utm_content=buffer9a242&utm_medium=social&utm_source=facebook.com&utm_campaign=buffer – Kaz Dragon May 28 '14 at 08:02

1 Answers1

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

Jonathan Lee
  • 477
  • 5
  • 9