如何有效地实现 std::tuple 使得空类型的元组本身就是空类型?
How to implement std::tuple efficiently such that a tuple of empty types is itself an empty type?
我正在实施 std::tuple
,我希望它在对象大小和编译时间方面尽可能高效。我遵循 here and here.
给出的建议
为了提高编译时性能,该实现不使用递归继承,而是使用 tuple_leaf
技巧的多重继承。此外,它在可能的情况下使用空基 class 优化来减小类型的大小。
为了确保始终应用空基数 class 优化,我的 tuple
实现本身是从基数 class 派生的,而不是将实现存储在成员变量中。但是,这会导致嵌套元组出现问题,因为 tuple_leaf
技术通过转换为基数 class 来工作。嵌套元组会导致歧义,因为同一类型的 tuple_leaf
在推导链中可能出现不止一次。
下面贴出了一个程序来简化问题。有没有一种简单的方法可以消除转换的歧义并允许程序在不抛出 assert
的情况下编译和执行?我考虑过检测嵌套元组大小写并以某种方式在其类型中编码每个 tuple_leaf
的多维位置,但这看起来很复杂并且可能会降低编译时性能。
#include <type_traits>
#include <cassert>
template<int i, class T, bool = std::is_empty<T>::value>
struct tuple_leaf
{
tuple_leaf(T x) : val(x) {}
T& get() { return val; }
T val;
};
template<int i, class T>
struct tuple_leaf<i,T,true> : private T
{
tuple_leaf(T x) : T(x) {}
T& get() { return *this; }
};
template<int i, class T1, class T2>
struct type_at
{
using type = T1;
};
template<class T1, class T2>
struct type_at<1,T1,T2>
{
using type = T2;
};
template<class T1, class T2>
struct tuple_base : tuple_leaf<0,T1>, tuple_leaf<1,T2>
{
tuple_base(T1 a, T2 b) : tuple_leaf<0,T1>(a), tuple_leaf<1,T2>(b) {}
template<int i>
tuple_leaf<i,typename type_at<i,T1,T2>::type> get_leaf()
{
// XXX how to disambiguate this conversion?
return *this;
}
};
// XXX deriving from tuple_base rather than
// making tuple_base a member is the root of the issue
template<class T1, class T2>
struct my_tuple : tuple_base<T1,T2>
{
my_tuple(T1 a, T2 b) : tuple_base<T1,T2>(a, b) {}
};
template<int i, class T1, class T2>
typename type_at<i,T1,T2>::type& get(my_tuple<T1,T2>& t)
{
return (t.template get_leaf<i>()).get();
}
template<class T1,class T2>
my_tuple<T1,T2> make_tuple(T1 a, T2 b)
{
return my_tuple<T1,T2>(a,b);
}
struct empty {};
int main()
{
auto tuple = make_tuple(empty(), make_tuple(empty(),empty()));
assert((std::is_empty<decltype(tuple)>::value));
assert(sizeof(tuple) == sizeof(empty));
get<0>(tuple);
return 0;
}
编译器输出:
$ clang-3.5 -std=c++11 repro.cpp
repro.cpp:47:12: error: ambiguous conversion from derived class 'tuple_base<empty, my_tuple<empty, empty> >' to base class 'tuple_leaf<0, empty, true>':
struct tuple_base<struct empty, struct my_tuple<struct empty, struct empty> > -> tuple_leaf<0, struct empty>
struct tuple_base<struct empty, struct my_tuple<struct empty, struct empty> > -> tuple_leaf<1, struct my_tuple<struct empty, struct empty> > -> struct my_tuple<struct empty, struct empty> -> tuple_base<struct empty, struct empty> -> tuple_leaf<0, struct empty>
return *this;
^~~~~
repro.cpp:63:22: note: in instantiation of function template specialization 'tuple_base<empty, my_tuple<empty, empty> >::get_leaf<0>' requested here
return (t.template get_leaf<i>()).get();
^
repro.cpp:80:3: note: in instantiation of function template specialization 'get<0, empty, my_tuple<empty, empty> >' requested here
get<0>(tuple);
^
1 error generated.
当你只有一把锤子时,一切看起来都像 "why not try CRTP"。因为CRTP用template
s解决了所有问题。
用派生的 class D
扩展 tuple_leaf
,并将 tuple_base
的类型传入。(或者,编写 template<class...>struct types{};
并将其传入 - 所有你need 是唯一区分两个不同元组的类型。
修改get_leaf
得到合适的class,现在没有歧义了。
问题:
首先,在没有 ICF 的情况下,这使得一堆原本相同的方法现在截然不同。
其次,如果你实现递归元组,这会很糟糕。以上依赖于包含子元组 X 的元组与子元组具有不同类型集的事实。
第三,when I tried it myself 使用上面的代码,我得到了非 1 大小的空结构。奇怪的。如果我绕过静态断言等,get<0>
段错误。这可能是您简化问题的产物,我不确定。
我正在实施 std::tuple
,我希望它在对象大小和编译时间方面尽可能高效。我遵循 here and here.
为了提高编译时性能,该实现不使用递归继承,而是使用 tuple_leaf
技巧的多重继承。此外,它在可能的情况下使用空基 class 优化来减小类型的大小。
为了确保始终应用空基数 class 优化,我的 tuple
实现本身是从基数 class 派生的,而不是将实现存储在成员变量中。但是,这会导致嵌套元组出现问题,因为 tuple_leaf
技术通过转换为基数 class 来工作。嵌套元组会导致歧义,因为同一类型的 tuple_leaf
在推导链中可能出现不止一次。
下面贴出了一个程序来简化问题。有没有一种简单的方法可以消除转换的歧义并允许程序在不抛出 assert
的情况下编译和执行?我考虑过检测嵌套元组大小写并以某种方式在其类型中编码每个 tuple_leaf
的多维位置,但这看起来很复杂并且可能会降低编译时性能。
#include <type_traits>
#include <cassert>
template<int i, class T, bool = std::is_empty<T>::value>
struct tuple_leaf
{
tuple_leaf(T x) : val(x) {}
T& get() { return val; }
T val;
};
template<int i, class T>
struct tuple_leaf<i,T,true> : private T
{
tuple_leaf(T x) : T(x) {}
T& get() { return *this; }
};
template<int i, class T1, class T2>
struct type_at
{
using type = T1;
};
template<class T1, class T2>
struct type_at<1,T1,T2>
{
using type = T2;
};
template<class T1, class T2>
struct tuple_base : tuple_leaf<0,T1>, tuple_leaf<1,T2>
{
tuple_base(T1 a, T2 b) : tuple_leaf<0,T1>(a), tuple_leaf<1,T2>(b) {}
template<int i>
tuple_leaf<i,typename type_at<i,T1,T2>::type> get_leaf()
{
// XXX how to disambiguate this conversion?
return *this;
}
};
// XXX deriving from tuple_base rather than
// making tuple_base a member is the root of the issue
template<class T1, class T2>
struct my_tuple : tuple_base<T1,T2>
{
my_tuple(T1 a, T2 b) : tuple_base<T1,T2>(a, b) {}
};
template<int i, class T1, class T2>
typename type_at<i,T1,T2>::type& get(my_tuple<T1,T2>& t)
{
return (t.template get_leaf<i>()).get();
}
template<class T1,class T2>
my_tuple<T1,T2> make_tuple(T1 a, T2 b)
{
return my_tuple<T1,T2>(a,b);
}
struct empty {};
int main()
{
auto tuple = make_tuple(empty(), make_tuple(empty(),empty()));
assert((std::is_empty<decltype(tuple)>::value));
assert(sizeof(tuple) == sizeof(empty));
get<0>(tuple);
return 0;
}
编译器输出:
$ clang-3.5 -std=c++11 repro.cpp
repro.cpp:47:12: error: ambiguous conversion from derived class 'tuple_base<empty, my_tuple<empty, empty> >' to base class 'tuple_leaf<0, empty, true>':
struct tuple_base<struct empty, struct my_tuple<struct empty, struct empty> > -> tuple_leaf<0, struct empty>
struct tuple_base<struct empty, struct my_tuple<struct empty, struct empty> > -> tuple_leaf<1, struct my_tuple<struct empty, struct empty> > -> struct my_tuple<struct empty, struct empty> -> tuple_base<struct empty, struct empty> -> tuple_leaf<0, struct empty>
return *this;
^~~~~
repro.cpp:63:22: note: in instantiation of function template specialization 'tuple_base<empty, my_tuple<empty, empty> >::get_leaf<0>' requested here
return (t.template get_leaf<i>()).get();
^
repro.cpp:80:3: note: in instantiation of function template specialization 'get<0, empty, my_tuple<empty, empty> >' requested here
get<0>(tuple);
^
1 error generated.
当你只有一把锤子时,一切看起来都像 "why not try CRTP"。因为CRTP用template
s解决了所有问题。
用派生的 class D
扩展 tuple_leaf
,并将 tuple_base
的类型传入。(或者,编写 template<class...>struct types{};
并将其传入 - 所有你need 是唯一区分两个不同元组的类型。
修改get_leaf
得到合适的class,现在没有歧义了。
问题:
首先,在没有 ICF 的情况下,这使得一堆原本相同的方法现在截然不同。
其次,如果你实现递归元组,这会很糟糕。以上依赖于包含子元组 X 的元组与子元组具有不同类型集的事实。
第三,when I tried it myself 使用上面的代码,我得到了非 1 大小的空结构。奇怪的。如果我绕过静态断言等,get<0>
段错误。这可能是您简化问题的产物,我不确定。