如果
std::tuple
的所有成员都是标准布局类型,那么std::tuple
本身是否是标准布局?存在用户定义的复制构造函数会使其非平凡,但我想知道它是否仍然可以是标准布局。
规范中的一句引语将是有用的。std::tuple
的所有成员都是标准布局类型,那么std::tuple
本身是否是标准布局?存在用户定义的复制构造函数会使其非平凡,但我想知道它是否仍然可以是标准布局。
规范中的一句引语将是有用的。不,标准布局要求所有非静态数据成员属于一个基类子对象或直接属于最派生的类型。而且std::tuple
的典型实现会为每个基类实现一个成员。
由于成员声明不能是pack expansion,在上述要求下,标准布局的tuple
不能有多个成员。一个实现仍然可以通过将所有tuple
"成员"存储在一个char[]
中,并通过reinterpret_cast
获取对象引用来规避此问题。元程序必须生成类布局,特殊成员函数必须重新实现。这将会相当麻烦。
char[]
内生成正确对齐的布局,因此实现将非常困难。而且我的建议只适用于POD成员,以允许简单地复制字节数组。) - Potatoswatterstd::aligned_storage
的组合来使块适当地对齐,并使用constexpr
函数来给出每个成员的偏移量。但是,你将无法获得EBO。 - Matthieu M.char[]
的实现中的reinterpret_cast
是否会违反(strict-)别名规则?有没有标准的方法来解决这个问题?(我问这个问题是因为我已经完成了这个实现,但后来意识到由于别名规则而存在问题...) - mitchnull受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;
}
实时示例: https://ideone.com/4LEnSS
以下是我不满意的几点:
这还不适合直接使用,只能将其视为概念验证。我可能会回来改进其中一些问题。或者,如果有人可以改进它,可以随意编辑。
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
时,不会使用尾部填充空间优化(没有测试确切条件)。“列表”方法可用于获取标准布局的元组
(以下示例存在一些不准确之处,但演示了这个想法):
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)...};
}
std::is_standard_layout
并进行编译时分支。然后您可以放心地知道自己在进行优化而不必了解类型本身的所有细节。 - GManNickG