[英]Perfect forwarding in std::make_unique<SomeWrapper<T>> not quite perfect
Given the following types:给定以下类型:
struct Point
{
int x;
int y;
Point(int x, int y) : x{ x }, y{ y } { }
};
class Widget
{
public:
std::string name;
Widget(std::string name) : name{ name } { }
};
template <typename T>
struct DataHolder
{
T value;
DataHolder(T value) : value {value} { }
};
Why does this code compile:为什么这段代码会编译:
auto compiles = std::make_unique<DataHolder<int>>(42);
auto alsoCompiles = std::make_unique<DataHolder<std::string>>("Hi");
and this code does not:并且此代码不会:
auto doesnt = std::make_unique<DataHolder<Point>>({20, 21}); // C2672 'std::make_unique': no matching overloaded function found and C7627 'initializer list': is not a valid template argument for '_Types'
auto alsoDoesnt = std::make_unique<DataHolder<Widget>>("Hello"); // C2664 'DataHolder<Widget>::DataHolder(T)': cannot convert argument 1 from 'const char [6]' to 'T'
How does std::make_unique<SomeWrapper<T>>()
determines if T
can be constructed within SomeWrapper
? std::make_unique<SomeWrapper<T>>()
如何确定T
是否可以在SomeWrapper
中构造? What makes int
and std::string
special in this case?在这种情况下,是什么让int
和std::string
变得特别? How can I make my types work the same way?如何使我的类型以相同的方式工作?
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<int>
有一个采用int
的构造函数,并且42
作为构造函数参数传递给构造DataHolder<int>
,它工作正常(类似于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")
). DataHolder<std::string>
有一个构造函数采用std::string
,并且"Hi"
作为构造函数参数传递,它可以隐式转换为std::string
,这很好(类似于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. std::make_unique<DataHolder<Point>>({20, 21})
不起作用,因为像{20, 21}
这样的花括号初始化列表没有类型, std::make_unique
的模板参数推导失败. 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:参数 P,其 A 是一个花括号初始化列表,但 P 不是std::initializer_list
、对一个的引用(可能是 cv 限定的)或对数组的引用:
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. DataHolder<Widget>
有一个使用Widget
的构造函数,而"Hello"
是一个const char[6]
,它可能会衰减为const char*
,然后需要两个用户定义的转换,一个从const char*
到std::string
,一个从std::string
到Widget
,但在一个隐式转换序列中只允许一个用户定义的转换。 (Similarly DataHolder<Widget>("Hello")
doesn't work either.) (同样DataHolder<Widget>("Hello")
也不起作用。)
If you pass Point
and std::string
directly, they would work fine.如果您直接传递Point
和std::string
,它们会正常工作。
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, ie work in the similar way as std::make_unique
.正如@YiFei 建议的那样,您可以制作DataHolder
的构造函数模板,并通过std::forward
将所有 arguments 转发到T
的构造函数以直接初始化数据成员,即与std::make_unique
类似的工作方式。
Eg例如
template <typename... Args>
DataHolder(Args&&... value) : value {std::forward<Args>(value)...} {}
Note that std::make_unique<DataHolder<Point>>({20, 21});
注意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);
但是你可以做std::make_unique<DataHolder<Point>>(20, 21);
instead, 20
and 21
are forwared to the constructor of Point
taking two int
s and then works fine.相反,将20
和21
转发给Point
的构造函数,采用两个int
,然后工作正常。 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.同样对于std::make_unique<DataHolder<Widget>>("Hello")
, "Hello"
被转发到Widget
的构造函数,采用std::string
, "Hello"
可以隐式转换为std::string
然后工作正常。
std::make_unique<DataHolder<Widget>>("Hello")
requires two conversions, one from const char*
to std::string
and another to Widget
. std::make_unique<DataHolder<Widget>>("Hello")
需要两次转换,一次从const char*
到std::string
,另一次到Widget
。 Only one implicit conversion is allowed.只允许进行一次隐式转换。
std::make_unique<DataHolder<Point>>(20, 21)
doesn't work because it's would be the same as calling DataHolder<Point>(20, 21)
but DataHolder
only takes a single argument. std::make_unique<DataHolder<Point>>(20, 21)
不起作用,因为它与调用DataHolder<Point>(20, 21)
相同,但DataHolder
只接受一个参数。
Your first two examples only require a single or no implicit conversion so work.您的前两个示例只需要一次或不需要隐式转换,因此可以正常工作。
To make these work you need DataHolder
to forward its constructor arguments to value
:要完成这些工作,您需要DataHolder
将其构造函数 arguments 转发给value
:
template <typename ...Args>
DataHolder(Args&& ... args) : value {std::forward<Args>(args)...} { }
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.