7

假设我有一个模板类

template <typename T> class foo;
template <typename... Args>
struct foo<std::tuple<Args...>> {
  std::tuple<Args...> t;
  foo(Args&&... args): t{std::forward<Args>(args)...} { }
};

我知道在这种情况下Args&&...是右值引用,我也可以写得很好,std::move而不是std::forward.

我也可以有一个带有左值引用的构造函数,就像这样

foo(const Args&... args): t{args...} { }

问题是是否可以获得与转发引用相同的行为,但对于明确的类型?我想要这个的原因是我可以使用类似的语法

foo bar({. . .}, {. . .}, . . ., {. . .});

如果我定义了foo(Args&&... args)构造函数,这可以工作,但不允许混合场景,我想用大括号括起来的初始化列表初始化一些成员元组元素,并从预先存在的对象实例复制其他元素。

4

1 回答 1

2

当然; 有一个花哨而简单的方法。

我将在下面详细介绍这种花哨的方式。首先是简单的方法:按价值取值。

template <typename... Args>
struct foo<std::tuple<Args...>> {
  std::tuple<Args...> t;
  foo(Args... args): t{std::forward<Args>(args)...} { }
};

真的,只要这样做。Args如果包含引用,转发用于做正确的事情。

取值比完美转发增加了一步,但成倍地减少了对过载的要求。


这是花哨的方式。我们键入擦除构造:

template<class T>
struct make_it {
  using maker=T(*)(void*);
  maker f;
  void* args;
  // make from move
  make_it( T&& t ):
    f([](void* pvoid)->T{
      return std::move(*static_cast<T*>(pvoid));
    }),
    args(std::addressof(t))
  {}
  // make from copy
  make_it( T const& t ):
    f([](void* pvoid)->T{
      return *(T const*)(pvoid);
    }),
    args(std::addressof(t))
  {}
  operator T()&&{return std::move(*this)();}
  T operator()()&&{ return f(args); }
};

这种类型通过复制或移动擦除构造。

template <typename... Args>
struct foo<std::tuple<Args...>> {
  std::tuple<Args...> t;
  foo(make_it<Args>... args): t{std::move(args)()...} { }
};

它不是完全透明的,但它是尽可能接近的。

需要双{{}}而不是单。这是一个用户定义的转换,因此不会隐式地完成另一个转换。我们可以添加一个通用的ctor:'

  // make from universal
  template<class U>
  make_it( U&& u ):
    f([](void* pvoid)->T{
      return std::forward<U>(*(U*)(pvoid));
    }),
    args(std::addressof(u))
  {}

U&&如果我们添加一个可用于隐式构造的 sfinae 奶嘴,效果会更好T

它有一些优势,但与仅按价值获取相比,它们是微不足道的。例如,在 C++17 中,不可移动类型在某些情况下可以完美前向构造。

于 2017-01-07T22:22:33.413 回答