50

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'}); 
0

4 Answers 4

54

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

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

5 Comments

+1 I learned something. that elevates std::initialiser_list<> to something beyond an ordinary library function.
Just for completeness. Here is a way to solve the problem: pastebin.com/huEGwnDt
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.
@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.
@4ZM If you call without parameters, the call is ambiguous.
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; } 

4 Comments

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.
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...
Works fine. You can even improve on that solution by implementing perfect forwarding for the initializer_list like this: pastebin.com/1ttGniBH ?
@4ZM that would be wrong. A std::initializer_list<T>&& is not a forwarding reference; it's an rvalue reference. Also, std::initializer_list is basically a const& to an array, so it's completely pointless to move or forward it.
7

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} ); 

Comments

0

i found a solution that does not work for the provided printme function in the question, but for similar ones:

this is what i started with:

template <typename V, typename C> bool in(const C& container, const V& element) { return std::find(container.begin(), container.end(), element) != container.end(); } 

which fails to compile for eg:

in({1, 2, 3}, 1); 

with

Candidate template ignored: couldn't infer template argument 'C' 

based off another answer here, we could add a template specialization for std::initializer_list:

template <typename V, typename C> bool in(const C& container, const V& element) { return std::find(container.begin(), container.end(), element) != container.end(); } template <typename V> bool in(const std::initializer_list<V>& container, const V& element) { return std::find(container.begin(), container.end(), element) != container.end(); } 

which looks silly.

but the following also works:

template <typename V, typename C = std::initializer_list<V>> bool in(const C& container, const V& element) { return std::find(container.begin(), container.end(), element) != container.end(); } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.