如果std::tuple
的所有成员都是standard layout types,那么std::tuple
本身是标准布局吗?用户定义的复制构造函数的存在使它变得非常重要,但我想知道它是否仍然是标准布局。
规范的引用会很好。
答案 0 :(得分:9)
不,标准布局要求所有非静态数据成员都属于一个基础子对象或直接属于最派生类型,std::tuple
的典型实现要求每个基类实现一个成员。
由于成员声明不能成为包扩展,根据上述要求,标准布局tuple
不能包含多个成员。通过将所有tuple
“成员”存储在一个char[]
内,并通过reinterpret_cast
获取对象引用,实现仍可以回避该问题。元程序必须生成类布局。必须重新实现特殊成员职能。这将是一个非常痛苦的事。
答案 1 :(得分:5)
受到PotatoSwatter的回答的启发,我一直致力于为C ++ 14创建标准布局元组。
代码实际上有效,但目前不适合使用,因为它涉及未定义的行为。将其视为概念验证。 这是我最终得到的代码:
#include <iostream>
#include <type_traits>
#include <array>
#include <utility>
#include <tuple>
//get_size
template <typename T_head>
constexpr size_t get_size()
{
return sizeof(T_head);
}
template <typename T_head, typename T_second, typename... T_tail>
constexpr size_t get_size()
{
return get_size<T_head>() + get_size<T_second, T_tail...>();
}
//concat
template<size_t N1, size_t... I1, size_t N2, size_t... I2>
constexpr std::array<size_t, N1+N2> concat(const std::array<size_t, N1>& a1, const std::array<size_t, N2>& a2, std::index_sequence<I1...>, std::index_sequence<I2...>)
{
return { a1[I1]..., a2[I2]... };
}
template<size_t N1, size_t N2>
constexpr std::array<size_t, N1+N2> concat(const std::array<size_t, N1>& a1, const std::array<size_t, N2>& a2)
{
return concat(a1, a2, std::make_index_sequence<N1>{}, std::make_index_sequence<N2>{});
}
//make_index_array
template<size_t T_offset, typename T_head>
constexpr std::array<size_t, 1> make_index_array()
{
return {T_offset};
}
template<size_t T_offset, typename T_head, typename T_Second, typename... T_tail>
constexpr std::array<size_t, (sizeof...(T_tail) + 2)> make_index_array()
{
return concat(
make_index_array<T_offset, T_head>(),
make_index_array<T_offset + sizeof(T_head),T_Second, T_tail...>()
);
}
template<typename... T_args>
constexpr std::array<size_t, (sizeof...(T_args))> make_index_array()
{
return make_index_array<0, T_args...>();
}
template<int N, typename... Ts>
using T_param = typename std::tuple_element<N, std::tuple<Ts...>>::type;
template <typename... T_args>
struct standard_layout_tuple
{
static constexpr std::array<size_t, sizeof...(T_args)> index_array = make_index_array<T_args...>();
char storage[get_size<T_args...>()];
//Initialization
template<size_t T_index, typename T_val>
void initialize(T_val&& val)
{
void* place = &this->storage[index_array[T_index]];
new(place) T_val(std::forward<T_val>(val));
}
template<size_t T_index, typename T_val, typename T_val2, typename... T_vals_rest>
void initialize(T_val&& val, T_val2&& val2, T_vals_rest&&... vals_rest)
{
initialize<T_index, T_val>(std::forward<T_val>(val));
initialize<T_index+1, T_val2, T_vals_rest...>(std::forward<T_val2>(val2), std::forward<T_vals_rest>(vals_rest)...);
}
void initialize(T_args&&... args)
{
initialize<0, T_args...>(std::forward<T_args>(args)...);
}
standard_layout_tuple(T_args&&... args)
{
initialize(std::forward<T_args>(args)...);
}
//Destruction
template<size_t T_index, typename T_val>
void destroy()
{
T_val* place = reinterpret_cast<T_val*>(&this->storage[index_array[T_index]]);
place->~T_val();
}
template<size_t T_index, typename T_val, typename T_val2, typename... T_vals_rest>
void destroy()
{
destroy<T_index, T_val>();
destroy<T_index+1, T_val2, T_vals_rest...>();
}
void destroy()
{
destroy<0, T_args...>();
}
~standard_layout_tuple()
{
destroy();
}
template<size_t T_index>
void set(T_param<T_index, T_args...>&& data)
{
T_param<T_index, T_args...>* ptr = reinterpret_cast<T_param<T_index, T_args...>*>(&this->storage[index_array[T_index]]);
*ptr = std::forward<T_param<T_index, T_args...>>(data);
}
template<size_t T_index>
T_param<T_index, T_args...>& get()
{
return *reinterpret_cast<T_param<T_index, T_args...>*>(&this->storage[index_array[T_index]]);
}
};
int main() {
standard_layout_tuple<float, double, int, double> sltuple{5.5f, 3.4, 7, 1.22};
sltuple.set<2>(47);
std::cout << sltuple.get<0>() << std::endl;
std::cout << sltuple.get<1>() << std::endl;
std::cout << sltuple.get<2>() << std::endl;
std::cout << sltuple.get<3>() << std::endl;
std::cout << "is standard layout:" << std::endl;
std::cout << std::boolalpha << std::is_standard_layout<standard_layout_tuple<float, double, int, double>>::value << std::endl;
return 0;
}
有一些我不满意的事情:
这还不适合按原样使用,实际上只是将其视为此状态下的概念验证。我可能会回过头来改进其中的一些问题。或者,如果其他人可以改进它,请随时编辑。
答案 2 :(得分:0)
std::tuple
不能是标准布局的一个原因,因为任何具有成员和带有成员的基类的类都是标准允许在派生甚至非空基类时进行空间优化。例如:
#include <cstdio>
#include <cstdint>
class X
{
uint64_t a;
uint32_t b;
};
class Y
{
uint16_t c;
};
class XY : public X, public Y
{
uint16_t d;
};
int main() {
printf("sizeof(X) is %zu\n", sizeof(X));
printf("sizeof(Y) is %zu\n", sizeof(Y));
printf("sizeof(XY) is %zu\n", sizeof(XY));
}
输出:
sizeof(X) is 16
sizeof(Y) is 2
sizeof(XY) is 16
以上显示该标准允许将类尾随填充用于派生类成员。类XY
有两个额外的uint16_t
成员,但其大小等于基类X
的大小。
换句话说,类XY
布局与没有基类的另一个类的布局相同,而XY
的所有成员都按地址排序,例如struct XY2 { uint64_t a; uint32_t b; uint16_t c; uint16_t d; };
。
使非标准布局的原因是派生类的大小不是基类和派生类成员的大小的函数。
请注意,struct
/ class
的大小是其成员之一与最大对齐要求的对齐的倍数。因此,对象阵列适合于这样的成员对齐。对于内置类型,通常为sizeof(T) == alignof(T)
。因此sizeof(X)
是sizeof(uint64_t)
的倍数。
我不确定该标准是否需要struct
的特殊处理,但g++-5.1.1
如果class
被struct
替换,则上述代码会产生不同的输出:
sizeof(X) is 16
sizeof(Y) is 2
sizeof(XY) is 24
换句话说,当涉及struct
时(不测试确切条件),不使用尾随填充空间优化。
答案 3 :(得分:0)
“列表”方法可用于获取标准布局tuple
(以下示例存在一些误差,但演示了此想法):
template <class... Rest>
struct tuple;
template <class T, class... Rest>
struct tuple<T, Rest...> {
T value;
tuple<Rest...> next;
};
template <>
struct tuple<> {};
namespace details {
template <size_t N>
struct get_impl {
template <class... Args>
constexpr static auto process(const tuple<Args...>& t) {
return get_impl<N - 1>::process(t.next);
}
};
template <>
struct get_impl<0> {
template <class... Args>
constexpr static auto process(const tuple<Args...>& t) {
return t.value;
}
};
}
template <size_t N, class... Args>
constexpr auto get(const tuple<Args...>& t) {
return details::get_impl<N>::process(t);
}
template <class... Args>
constexpr auto make_tuple(Args&&... args) {
return tuple<Args...>{std::forward<Args>(args)...};
}