Search code examples
c++c++11initializer-listlist-initialization

Issue with object initialization from initializer list


I have the following code:

    class A {
        public: 
        A(std::vector<std::shared_ptr<int>>){}
    };

    auto x = std::make_shared<int>(0);
    auto y = std::make_shared<int>(1);

    auto list = {x, y};
    auto res = std::make_shared<A>({x, y});

In the example if I pass to res variable list it compiles, otherwise as in the case of using initializer list directly it fails http://ideone.com/8jYsDY

I guess it has to do with the way type deduction works when initializer_list are involved. If this is standard conformant some reference would be good.


Solution

  • std::make_shared deduces its second template parameter from the arguments to the function call. A braced-init-list is not an expression, and as such, has no type. Hence template argument deduction cannot deduce a type from it.

    From §14.8.2.5/5 [temp.deduct.type]

    The non-deduced contexts are:
      — ...
      — 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 ]

    auto, however, is a special case that is allowed to deduce std::initializer_list<T> from a braced-init-list.

    §7.1.6.4/7 [dcl.spec.auto]

    ... Otherwise, obtain P from T by replacing the occurrences of auto with either a new invented type template parameter U or, if the initializer is a braced-init-list, with std::initializer_list<U>. Deduce a value for U using the rules of template argument deduction from a function call (14.8.2.1), where P is a function template parameter type and the initializer is the corresponding argument. If the deduction fails, the declaration is ill-formed. Otherwise, the type deduced for the variable or return type is obtained by substituting the deduced U into P. [ Example:

     auto x1 = { 1, 2 }; // decltype(x1) is std::initializer_list<int>
     auto x2 = { 1, 2.0 }; // error: cannot deduce element type
    

    —end example ]

    In your example, the variable list has the type initializer_list<shared_ptr<int>>, and when you pass it to make_shared, a vector can be constructed from it, which is then used to direct-initialize the A instance.

    Other options are to construct a vector

    auto res = std::make_shared<A>(std::vector<std::shared_ptr<int>>{x, y});
    

    construct an A, which will then be moved

    auto res = std::make_shared<A>(A{{x, y}});
    

    or specify the template parameters for make_shared explicitly

    auto res = std::make_shared<A, std::initializer_list<std::shared_ptr<int>>>({x, y});