initializer_list and template type deduction

11,678

Solution 1

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

Solution 2

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

Solution 3

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} );
Share:
11,678

Related videos on Youtube

4ZM
Author by

4ZM

Updated on June 11, 2022

Comments

  • 4ZM
    4ZM almost 2 years

    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'});
    
  • Walter
    Walter over 11 years
    +1 I learned something. that elevates std::initialiser_list<> to something beyond an ordinary library function.
  • 4ZM
    4ZM over 11 years
    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.
  • 4ZM
    4ZM over 11 years
    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
    4ZM over 11 years
    Just for completeness. Here is a way to solve the problem: pastebin.com/huEGwnDt
  • 4ZM
    4ZM over 11 years
    Works fine. You can even improve on that solution by implementing perfect forwarding for the initializer_list like this: pastebin.com/1ttGniBH ?
  • Joseph Mansfield
    Joseph Mansfield over 9 years
    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.
  • dshepherd
    dshepherd about 8 years
    @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.
  • 김선달
    김선달 almost 4 years
    @4ZM If you call without parameters, the call is ambiguous.