initializer_list and template type deduction
Asked Answered
F

3

49

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'});
Forbidding answered 14/9, 2012 at 20:23 Comment(0)
S
53

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

Stomodaeum answered 14/9, 2012 at 20:51 Comment(5)
+1 I learned something. that elevates std::initialiser_list<> to something beyond an ordinary library function.Harwood
Just for completeness. Here is a way to solve the problem: pastebin.com/huEGwnDtForbidding
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.Kerb
@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.Consignee
@Forbidding If you call without parameters, the call is ambiguous.Croquette
G
14

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;
}
Galactometer answered 14/9, 2012 at 23:11 Comment(3)
Sure, but that would make the other versions fail, e.g. printme(std::vector<char>({'a', 'b', 'c'}));. Template specialization won't work here unfortunately.Forbidding
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...Forbidding
Works fine. You can even improve on that solution by implementing perfect forwarding for the initializer_list like this: pastebin.com/1ttGniBH ?Forbidding
S
6

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} );
Stampede answered 14/9, 2012 at 20:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.