如果std::tuple
的所有成員都是standard layout types,那std::tuple
本身是標準的佈局嗎?用戶定義的拷貝構造函數的存在使得它不是微不足道的,但我想知道它是否仍然是標準佈局。std ::元組和標準佈局
來自規格的報價是很好的。
如果std::tuple
的所有成員都是standard layout types,那std::tuple
本身是標準的佈局嗎?用戶定義的拷貝構造函數的存在使得它不是微不足道的,但我想知道它是否仍然是標準佈局。std ::元組和標準佈局
來自規格的報價是很好的。
不,標準佈局要求所有非靜態數據成員都屬於一個基本子對象或直接屬於最大派生類型,std::tuple
的典型實現爲每個基類實現一個成員。
由於會員聲明不能是包擴展,根據上述要求,標準佈局tuple
不能有多個成員。通過將所有tuple
「成員」存儲在一個char[]
中,並通過reinterpret_cast
獲得對象引用,實現仍然可以迴避該問題。元程序必須生成類佈局。特殊成員函數將不得不重新實現。這將是一個非常痛苦的事情。
典型的實現,是的,但不是_good_實現([根據霍華德](http://stackoverflow.com/a/9643480/636019):-P);我懷疑他們應該要求標準佈局類型的元組只能是標準佈局... – ildjarn 2012-03-23 03:11:03
@ildjarn請參閱編輯答案 – Potatoswatter 2012-03-23 03:11:44
請參閱編輯的註釋。 ; - ] – ildjarn 2012-03-23 03:12:17
受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
時未使用(未針對確切條件進行測試)。
如果你想知道這個,因爲你想要一個優化機會,你應該使用'std :: is_standard_layout'並採取編譯時分支。然後,您可以在不知道類型本身的所有細節的情況下知道自己是最優的。 – GManNickG 2012-03-23 03:18:58
看起來好像會這樣,但是我在標準的「元組」一節中找不到任何提及的「標準佈局」。其他地方可能會提到,但如果是這樣,我還沒有發現它。 – 2012-03-23 03:21:48