std :: make_unique' s(以及emplace,emplace_back' s)对initializer_list参数的笨拙扣除

时间:2017-12-31 15:11:13

标签: c++ c++14 unique-ptr initializer-list c++17

说我有这个结构:

struct position
{
  int x, y;
};

和另一个将其作为构造函数参数的类:

class positioned
{
public:
  positioned(position p) : pos(p) {}
private:
  position pos;
};

我怎样才能得到简单的

auto bla = std::make_unique<positioned>({1,2});

上班?

目前,编译器尝试通过initializer_list<int>匹配并调用make_unique的数组变体,这很愚蠢,因为positioned只有一个构造函数。 emplaceemplace_back函数也出现了同样的问题。几乎任何将其可变参数模板参数转发给类的构造函数的函数似乎都表现出这种行为。

我知道我可以通过

来解决这个问题
  1. positioned提供两个int参数构造函数,并将{}放入对make_unique的调用中,或
  2. make_unique的参数类型明确指定为position{1,2}
  3. 在我看来(在make_unique实现中有一些努力),两者看起来都过于冗长,如果没有对参数类型进行过度规范,就可以解决这个问题。

    这是make_unique实施中的可解决的缺陷还是这是一个无法解决的,无趣的边缘案例,没有人应该关心?

3 个答案:

答案 0 :(得分:7)

当给出braced-init-list时,函数模板参数推导不起作用;它只能根据实际表达方式工作。

还应该注意,positioned无法从{1, 2}初始化列表。这将尝试调用两个参数构造函数,positioned没有这样的构造函数。您需要使用positioned({1, 2})positioned{{1, 2}}

因此,一般的解决方案是让make_unique以某种方式神奇地重现它正在构建的类型的每个可能的构造函数的签名。这在C ++中显然不是一件合理的事情。

另一种方法是使用lambda来创建对象,并编写一个替代的make函数,使用C ++ 17保证的省略规则将返回的prvalue应用于内部{{1}表达式:

new

你甚至可以放弃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});}); 参数:

typename T

答案 1 :(得分:1)

据我所知,最实际的方法是摆脱大括号,并添加构造函数来分散地获取参数:

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);
}

如果position有多个ctor,您可能想为positioned创建一个可变参数模板ctor来获取一些任意参数并将它们传递给position& #39; 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
}

这样,参数就会从make_unique转发到positionedposition。这确实至少在效率方面也有一些潜在的优势 - 而不是使用参数来创建一个临时对象,然后传递给初始化底层对象,它将原始对象直接传递(引用)给ctor。基础对象,所以我们只在原地构建一次。

请注意,这确实为我们提供了相当多的功能。例如,我们假设positioned本身就是一个模板,而基础position是一个模板参数:

#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);
}

兼容性:我认为这需要C ++ 14或更高版本,因为make_unique得到了它的转发/可变参数ctor。

答案 2 :(得分:0)

问题是像{1, 2}这样的初始化者无法推断:以下代码不起作用(有充分理由:{1, 2}的类型是什么?)。

template<typename U>
void foo(U args) { }

foo({1, 2});

make_unique只是这个主题的一个更复杂的变体。

这里详细解释了根本原因:initializer_list and template type deduction