元组聚合构造,可以推断类型并省略移动/复制构造函数调用?

6
考虑以下mypair类(我不确定这是否是做事情的最佳方式,但它似乎管用):
#include <iostream>

struct A
{
  A() {}
  A(const A&) { std::cout << "Copy" << std::endl; }
  A(A&&) { std::cout << "Move" << std::endl; }
  std::string s;
};

template <class T0, class T1>
struct mypair
{
  T0 x0;
  T1 x1;
};

template <class T0, class T1, int N = -1>
struct get_class {};

template<class T0, class T1>
struct get_class<T0, T1, 0>
{
  static T0& get_func(mypair<T0, T1>& x) { return x.x0; }
  static const T0& get_func(const mypair<T0, T1>& x) { return x.x0; }
  static T0&& get_func(mypair<T0, T1>&& x) { return std::move(x.x0); }
};

template<class T0, class T1>
struct get_class<T0, T1, 1>
{
  static T1& get_func(mypair<T0, T1>& x) { return x.x1; }
  static const T1& get_func(const mypair<T0, T1>& x) { return x.x1; }
  static T1&& get_func(mypair<T0, T1>&& x) { return std::move(x.x1); }
};

template <int N, class T0, class T1>
auto get(mypair<T0, T1>& x) -> decltype(get_class<T0,T1,N>::get_func(x)) 
{ return get_class<T0,T1,N>::get_func(x); }

#define MAKE_PAIR(x1, x2) mypair<decltype(x1), decltype(x2)>{x1, x2}

int main()
{
  auto x = MAKE_PAIR(A(), A());
  get<0>(x);
  get<1>(x);
}

(ideone链接)

回答 C++11中什么时候聚合初始化是有效的 说我们可以通过进行聚合初始化来省略复制/移动。

因此,我们可以使用MAKE_PAIR构造mypair而无需执行任何移动或复制操作。

我想将MAKE_PAIR泛化为MAKE_TUPLE,即接受任意数量的参数。

要求如下(与MAKE_PAIR相同):

(1) 类型被推断。
(2) 当从临时对象构造时省略移动/复制(即在原地进行构造)。

现有的库解决方案(例如Boost)也可以,尽管我更喜欢接受右值引用的东西。或者只是在这里编写的代码也很好,或者两者混合使用。

如果可能的话,我希望它能优化出空成员,同时仍然省略移动/复制构造函数调用,但我感觉这要求太高了。


1
你不能像初始化聚合一样省略元组中的复制/移动,如果你是这么想的话。(如果你真的想要一个聚合体,那就自己建立一个。)下一个最好的选择是 std::make_tuple 使用最佳的构造函数,这真的不应该有任何影响。通过元组提供的花哨结构来支持它们的成本就是它们不是聚合体。我不确定为什么你会如此担心这个额外的步骤,几乎肯定在你的代码中会有更大的性能瓶颈。 - Kerrek SB
2个回答

1

make_tuple 声明为 make_tuple(Args&&...),因此 make_tuple(A{foo},B{blah}) 可以(根据编译器的调用约定)并且几乎肯定会省略上述构造中的移动,如果您给它临时参数。

您无法将 make_tuple 泛化以强制执行原地构造。这是一个反例。

struct A {
   A (int);
};

struct B {
   B (int, int);
};

如果我尝试编写my_make_tuple,它将通过就地构造来构造一个tuple<A,B>,那么它必须计算出my_make_tuple(1,2,3)make_tupe(A{1},B{2,3})的值等效。
很明显,my_make_tuple(1,2,3)必须与make_tupe(B{1,2},A{3})的值等效。
虽然A和B的构造函数可能会有歧义重载,并且由于C++没有反射,但对我来说,在一般情况下,你所要求的是不可能实现的。 make_tuple之所以有效,是因为输入类型在参数列表中被清晰地分离。您不必担心移动运算符的成本,对于所有合理的情况,它们都非常便宜。此外,它们可以省略。
如果性能至关重要,请内联构造函数并让编译器解决问题。

1

在C++0x标准库中已经有这样的功能-一个std::make_tuple。即使你感到无聊,也可以自己制作。

template<typename... T> std::tuple<T...> make_tuple(T...&& refs) {
    return std::tuple<T...> { std::forward<T>(refs)... };
}

1
我相信std::make_tuple调用移动构造函数而不是就地构造元素。你能否在任何编译器上向我展示代码,其中std::make_tuple(A())不打印“Move”?MAKE_PAIR不打印“Move”,它跳过省略对移动构造函数的调用。 - Clinton
1
std::make_tuple 使用 std::decay - Luc Danton
1
官方的make_tuple函数还会对reference_wrappers进行一些特殊的类型转换。make_tuple(ref(some_int))实际上会生成一个tuple<int&>对象。 - sellibitze

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接