Say I have this struct:
struct position
{
int x, y;
};
and another class that takes this as constructor argument:
class positioned
{
public:
positioned(position p) : pos(p) {}
private:
position pos;
};
How can I get the simple
auto bla = std::make_unique<positioned>({1,2});
to work?
Currently, the compiler tries to match through the initializer_list<int>
and invoke the array variant of make_unique
, which is silly, because positioned
has only one constructor. The same issue arises for emplace
and emplace_back
functions. Pretty much any function that forwards its variadic template arguments to a class's constructor seems to exhibit this behaviour.
I understand I can resolve this by
positioned
a two int
argument constructor and dropping the {}
in the call to make_unique
, ormake_unique
as position{1,2}
.Both seem overly verbose, as it seems to me (with some effort in the make_unique implementation), this can be resolved without this overspecification of the argument type.
Is this a resolvable defect in the make_unique
implementation or is this an unresolvable, uninteresting edge-case no one should care about?
Function template argument deduction does not work when being given a braced-init-list; it only works based on actual expressions.
It should also be noted that positioned
cannot be list initialized from {1, 2}
anyway. This will attempt to call a two argument constructor, and positioned
has no such constructor. You would need to use positioned({1, 2})
or positioned{{1, 2}}
.
As such, the general solution would be to have make_unique
somehow magically reproduce the signature of every possible constructor for the type it is constructing. This is obviously not a reasonable thing to do in C++ at this time.
An alternative would be to use a lambda to create the object, and write an alternative make
function use C++17's guaranteed elision rules to apply the returned prvalue to the internal new
expression:
template<typename T, typename Func, typename ...Args>
std::unique_ptr<T> inject_unique(Func f, Args &&...args)
{
return std::unique_ptr<T>(new auto(f(std::forward<Args>(args)...)));
}
auto ptr = inject_unique<positioned>([]() {return positioned({1, 2});});
You can even ditch the typename T
parameter:
template<typename Func, typename ...Args>
auto inject_unique(Func f, Args &&...args)
{
using out_type = decltype(f(std::forward<Args>(args)...));
return std::unique_ptr<out_type>(new auto(f(std::forward<Args>(args)...)));
}
auto ptr = inject_unique([]() {return positioned({1, 2});});