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});});
As far as I can see, the most practical way to do this is probably to get rid of the braces, and add constructors to take the arguments discretely:
struct position
{
int x, y;
position(int x, int y) : x(x), y(y) {}
};
class positioned
{
public:
positioned(int x, int y) : pos(x, y) {}
private:
position pos;
};
int main() {
auto bla = std::make_unique<positioned>(1,2);
}
If position
had more than one ctor, you'd probably want to create a variadic template ctor for positioned
to take some arbitrary parameters and pass them through to position
's ctor(s).
struct position
{
int x, y;
position(int x, int y) : x(x), y(y) {}
position(int b) : x(b), y(b) {} // useless--only to demo a different ctor
};
class positioned
{
public:
template <class... Args>
positioned(Args&&... a) : pos(std::forward<Args>(a)...) {}
private:
position pos;
};
int main() {
auto bla = std::make_unique<positioned>(1,2); // use 1st ctor
auto bla2 = std::make_unique<positioned>(1); // use 2nd ctor
}
This way the arguments get forwarded through from make_unique
to positioned
to position
. This does give at least some potential advantage in efficiency as well--instead of using the arguments to create a temporary object, which is then passed to initialize the underlying object, it passes (references to) the original objects directly to the ctor for the underlying object, so we only construct it once, in-place.
Note that this does give us a fair amount of versatility. For example, let's assume positioned
were itself a template, and the underlying position
a template argument:
#include <memory>
struct position2
{
int x, y;
position2(int x, int y) : x(x), y(y) {}
};
struct position3 {
int x, y, z;
position3(int x, int y, int z) : x(x), y(y), z(z) {}
};
template <class Pos>
class positioned
{
public:
template <class... Args>
positioned(Args&&... a) : pos(std::forward<Args>(a)...) {}
private:
Pos pos;
};
int main() {
auto bla = std::make_unique<positioned<position2>>(1,2);
auto bla2 = std::make_unique<positioned<position3>>(1, 2, 3);
}
Compatibility: I believe this requires C++ 14 or newer, since that's when make_unique
got its forwarding/variadic ctor.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With