繁体   English   中英

std :: make_unique(和emplace,emplace_back)对initializer_list参数的尴尬演绎

[英]std::make_unique's (and emplace, emplace_back's) awkward deduction for initializer_list arguments

说我有这个结构:

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}

两者看起来都过于冗长,因为在我看来(在make_unique实现中付出了一些努力),这可以在没有参数类型的过度规范的情况下得到解决。

这是make_unique实现中的一个可解决的缺陷,还是一个无法解决的make_unique边缘情况?

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

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

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

另一种方法是使用lambda来创建对象,并编写另一个make函数,使用C ++ 17的保证省略规则将返回的prvalue应用于内部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参数:

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

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

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,你可能想要创建一个可变参数模板ctor,用于positioned以获取一些任意参数并将它们传递给position的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。

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

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

foo({1, 2});

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

这里详细解释了根本原因: initializer_list和模板类型推导

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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