48

Consider the function:

template<typename T>
void printme(T&& t) {
  for (auto i : t)
    std::cout << i;
}

or any other function that expects one parameter with a begin()/end() - enabled type.

Why is the following illegal?

printme({'a', 'b', 'c'});

When all these are legitimate:

printme(std::vector<char>({'a', 'b', 'c'}));
printme(std::string("abc"));
printme(std::array<char, 3> {'a', 'b', 'c'});

We can even write this:

const auto il = {'a', 'b', 'c'};
printme(il);

or

printme<std::initializer_list<char>>({'a', 'b', 'c'});
Praetorian
  • 106,671
  • 19
  • 240
  • 328
4ZM
  • 1,463
  • 1
  • 11
  • 21

3 Answers3

50

Your first line printme({'a', 'b', 'c'}) is illegal because the template argument T could not be inferred. If you explicitly specify the template argument it will work, e.g. printme<vector<char>>({'a', 'b', 'c'}) or printme<initializer_list<char>>({'a', 'b', 'c'}).

The other ones you listed are legal because the argument has a well-defined type, so the template argument T can be deduced just fine.

Your snippet with auto also works because il is considered to be of type std::initializer_list<char>, and therefore the template argument to printme() can be deduced.


The only "funny" part here is that auto will pick the type std::initializer_list<char> but the template argument will not. This is because § 14.8.2.5/5 of the C++11 standard explicitly states that this is a non-deduced context for a template argument:

A function parameter for which the associated argument is an initializer list (8.5.4) but the parameter does not have std::initializer_list or reference to possibly cv-qualified std::initializer_list type. [Example:

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

— end example ]

However with auto, § 7.1.6.4/6 has explicit support for std::initializer_list<>

if the initializer is a braced-init-list (8.5.4), with std::initializer_list<U>.

Community
  • 1
  • 1
Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
  • 1
    +1 I learned something. that elevates `std::initialiser_list<>` to something beyond an ordinary library function. – Walter Sep 14 '12 at 22:34
  • 5
    Just for completeness. Here is a way to solve the problem: http://pastebin.com/huEGwnDt – 4ZM Sep 18 '12 at 06:31
  • 8
    Do we know *why* this is the case? It seems pretty strange to me that if I want to allow a template function (maybe a range-based algorithm) to take a initializer list argument, I have to provide an overload for `std::initializer_list`. – Joseph Mansfield Nov 28 '14 at 09:56
  • 4
    @JosephMansfield I haven't managed to find a definitive answer but I suspect it's to do with braces being used for uniform initialization. The call `g({1, 2, 3})` could also be taken to mean `g(Foo(1, 2, 3))` where `Foo` is any class with a constructor taking three ints. – dshepherd Mar 27 '16 at 21:59
  • @4ZM If you call without parameters, the call is ambiguous. – 김선달 Jul 16 '20 at 01:40
13

You can also overload the function to explicitly take an argument of type initializer_list.

template<typename T>
void printme(std::initializer_list<T> t) {
  for (auto i : t)
    std::cout << i;
}
John Schug
  • 429
  • 1
  • 3
  • 8
  • Sure, but that would make the other versions fail, e.g. `printme(std::vector({'a', 'b', 'c'}));`. Template specialization won't work here unfortunately. – 4ZM Sep 16 '12 at 20:52
  • Oh, but that's great! Thank you. I thought I had tried this, but I was wrong. Template specialization works fine here. Since the function can be implemented in _exactly_ the same way, all that remains is how to figure out how to make one of them call the other... – 4ZM Sep 17 '12 at 05:57
  • 1
    Works fine. You can even improve on that solution by implementing perfect forwarding for the initializer_list like this: http://pastebin.com/1ttGniBH ? – 4ZM Sep 20 '12 at 08:28
5

This is specifically covered under § 14.8.2.5/5

A function parameter for which the associated argument is an initializer list but the parameter does not have std::initializer_list or reference to possibly cv-qualified std::initializer_list type. [ Example:

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

—end example ]

To make it work, you can specify the template argument type explicitly.

printme<std::initializer_list<int>>( {1,2,3,4} );
Community
  • 1
  • 1
Praetorian
  • 106,671
  • 19
  • 240
  • 328