Why does this code compile:
auto compiles = std::make_unique<DataHolder<int>>(42);
auto alsoCompiles = std::make_unique<DataHolder<std::string>>("Hi");
DataHolder<int> has a constructor taking int, and 42 is passed as the constructor argument to construct DataHolder<int>, which works fine (similarly as DataHolder<int>(42)).
DataHolder<std::string> has a constructor taking std::string, and "Hi" is passed as the constructor argument, it could convert to std::string implicitly, which works fine (similarly as DataHolder<std::string>("Hi")).
and this code does not:
auto doesnt = std::make_unique<DataHolder<Point>>({20, 21});
auto alsoDoesnt = std::make_unique<DataHolder<Widget>>("Hello");
std::make_unique<DataHolder<Point>>({20, 21}) doesn't work because braced-init-list like {20, 21} doesn't have type, template argument deduction for std::make_unique fails on it. This belongs to non deduced contexts.
- The parameter P, whose A is a braced-init-list, but P is not
std::initializer_list, a reference to one (possibly cv-qualified), or a reference to an array:
DataHolder<Widget> has a constructor taking Widget, and "Hello" is a const char[6] which could decay to const char*, then two user-defined conversions are required, one from const char* to std::string, one from std::string to Widget, but only one user-defined conversion is allowed in one implicit conversion sequence. (Similarly DataHolder<Widget>("Hello") doesn't work either.)
If you pass Point and std::string directly, they would work fine.
auto doesnt = std::make_unique<DataHolder<Point>>(Point{20, 21});;
auto alsoDoesnt = std::make_unique<DataHolder<Widget>>(std::string{"Hello"});
As @YiFei suggested, you can make DataHolder's constructor template, and forward all the arguments by std::forward to the constructor of T to initialize the data member directly, i.e. work in the similar way as std::make_unique.
E.g.
template <typename... Args>
DataHolder(Args&&... value) : value {std::forward<Args>(value)...} {}
Note that std::make_unique<DataHolder<Point>>({20, 21}); still doesn't work for the reason above. But you can do std::make_unique<DataHolder<Point>>(20, 21); instead, 20 and 21 are forwared to the constructor of Point taking two ints and then works fine. Similarly for std::make_unique<DataHolder<Widget>>("Hello"), "Hello" is forwarded to the constructor of Widget taking std::string, "Hello" could convert to std::string implicitly and then works fine.