15

Initializer list expressions are really convenient for initializing C++ containers:

std::vector<int>({1, 2, 3})

...but it seems that a brace-enclosed initializer list expression, like {1,2,3} will only bind to a function that takes a std::initializer_list<int> - it doesn't seem to bind to a universal (forwarding) reference:

template <class T>
void foo(T&& v)
{
  std::vector<int>(std::forward<T>(v));
}

int main()
{
  foo({1, 2, 3})
}

This outputs:

test2.cpp:11:6: note: template<class U> void foo(U&&)
test2.cpp:11:6: note:   template argument deduction/substitution failed:
test2.cpp:33:13: note:   couldn't deduce template parameter ‘U’

(This was the result with GCC 4.7.2.)

This unfortunately means we can't forward an initializer list expression. Since it would be very convenient to do that, I'd like to ask why is it that this doesn't work? Why can't a brace enclosed initializer list expression bind to a forwarding reference? Or is this allowed, and perhaps my compiler is just too old?

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Siler
  • 7,745
  • 5
  • 46
  • 107
  • 1
    It's of no use to forward an `initializer_list`: It already has reference semantics, and its elements are const, hence not movable. – dyp Feb 06 '15 at 17:07
  • 1
    It doesn't matter if they're movable - a universal forwarding reference doesn't necessarily imply moving, it just implies forwarding along the parameter as is – Siler Feb 06 '15 at 17:08
  • 3
    Yes, but a braced-init-list doesn't have a type. Hence, you cannot forward it "as-is". – dyp Feb 06 '15 at 17:09
  • 1
    Related: http://stackoverflow.com/q/17582667 – dyp Feb 06 '15 at 17:09
  • 3
    A braced-init-list is NOT an expression. It therefore doesn't have a type. This fact is a source of endless confusion. – Casey Feb 06 '15 at 17:20
  • 1
    @dyp, @Casey ah okay, I understand... although, I thought `std::initializer_list` was the actual type - because it works with `auto` – Siler Feb 06 '15 at 17:21
  • 1
    @Siler, A source of frustration for Scott Meyers in his [talk](https://www.youtube.com/watch?v=wQxj20X-tIU#t=1784) – chris Feb 06 '15 at 17:30

2 Answers2

17

It's not that it can't bind to the parameter of your function; it's just that the compiler is unable to detect the type of your template. This compiles:

#include <vector>

template <class T>
void foo(T&& v)
{
  std::vector<int>(std::forward<T>(v));
}

int main()
{
  foo(std::initializer_list<int>{1, 2, 3});
}
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Drax
  • 11,247
  • 5
  • 37
  • 77
6

The initializer list cannot be deduced in this case. This is actually covered explicitly by the standard in [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, [...]. Otherwise, an initializer list argument causes the parameter to be considered a non-deduced context (14.8.2.5). [ Example:

template<class T> void f(std::initializer_list<T>);
f({1,2,3}); // T deduced to int
f({1,"asdf"}); // error: T deduced to both int and const char*

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

The example here for g is exactly your case - T is not a dependent type, so this is considered to be a non-deduced context. The compiler is correct to reject your code.

Barry
  • 247,587
  • 26
  • 487
  • 819