简体   繁体   English

std::make_unique 中的完美转发<somewrapper<t> &gt; 不太完美</somewrapper<t>

[英]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?在这种情况下,是什么让intstd::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 .这属于非推断上下文

  1. 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::stringWidget ,但在一个隐式转换序列中只允许一个用户定义的转换。 (Similarly DataHolder<Widget>("Hello") doesn't work either.) (同样DataHolder<Widget>("Hello")也不起作用。)

If you pass Point and std::string directly, they would work fine.如果您直接传递Pointstd::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.相反,将2021转发给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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM