所以我想了很多,意识到虽然不能使用存储对象内部的填充,但可以尝试安排Node的成员,使它们占用最少的空间。当然,您可以对其进行硬编码,但我认为更优雅的方式是使其自动化。
要找到最小的排列,我们需要生成所有可能的排列并选择最小的排列。生成所有排列可以使用元函数permutations 完成,它生成一组类型的所有排列:
template <typename P1, typename P2>
struct merge {};
template <template <typename...> class P, typename... Ts, typename... Us>
struct merge<P<Ts...>, P<Us...>> {
using type = P<Ts..., Us...>;
};
template <typename T, typename P>
struct prepend {};
template <typename T, template <typename...> class P, typename... Packs>
struct prepend<T, P<Packs...>> {
using type = P<typename merge<P<T>, Packs>::type...>;
};
// N is the number of rotations to go
template <std::size_t N, typename Pack, typename = void>
struct permutations_impl {};
template <template <typename...> class P, typename... Ts>
struct permutations_impl<0, P<Ts...>> {
// All rotations done, break the recursion
using type = P<>;
};
template <std::size_t N, template <typename...> class P, typename T>
struct permutations_impl<N, P<T>> {
using type = P<P<T>>;
};
template <std::size_t N, template <typename...> class P, typename F, typename... Rest>
struct permutations_impl<N, P<F, Rest...>, std::enable_if_t<(sizeof...(Rest) && N != 0)>> {
using PermuteRest = typename permutations_impl<sizeof...(Rest), P<Rest...>>::type;
using NextRotation = typename permutations_impl<N-1, P<Rest..., F>>::type;
using type = typename merge<typename prepend<F, PermuteRest>::type, NextRotation>::type;
};
template <typename Pack>
struct permutations {};
template <template <typename...> class P, typename... Ts>
struct permutations<P<Ts...>> {
using type = typename permutations_impl<sizeof...(Ts), P<Ts...>>::type;
};
要选择所有排列中最小的,我们可以这样做:
template <typename Pack>
struct min_size_element {
static constexpr std::size_t min_index(std::initializer_list<std::size_t> l) {
std::size_t min = *l.begin();
std::size_t idx = 0, result = 0;
for(auto it = l.begin(); it != l.end(); ++it, ++idx) {
if(*it < min) min = *it, result = idx;
}
return result;
}
};
template <typename... Ts>
struct min_size_element<std::tuple<Ts...>> : min_size_element<void> {
using type = std::tuple_element_t<min_index({sizeof(Ts)...}), std::tuple<Ts...>>;
};
template <typename... Ts>
struct smallest_tuple {
using tuples = typename permutations<std::tuple<Ts...>>::type;
using type = typename min_size_element<tuples>::type;
};
Node 类随后会将键、值和使用的标志存储在由smallest_tuple 选择的元组中。元素需要按类型访问(因为我们不知道它们的索引),因此键和值元素需要包装在唯一的包装器类型中。 Node 类的实现,其中包含键和值的包装器和访问器,可能如下所示:
template <typename K, typename V>
class Node {
union KeyWrap {
struct{} _;
K key;
constexpr KeyWrap() : _() {}
};
union ValueWrap {
struct{} _;
V value;
constexpr ValueWrap() : _() {}
};
// Need to wrap key and value types because tuple elements are accessed by type
using Storage = typename detail::smallest_tuple<bool, KeyWrap, ValueWrap>::type;
Storage mStorage;
public:
constexpr Node() = default;
~Node() {
if(this->used()) {
this->key().~K();
this->value().~V();
}
}
Node& operator=(std::pair<K, V> entry) {
if(this->used()) {
this->key() = std::move(entry.first);
this->value() = std::move(entry.second);
} else {
new(&std::get<KeyWrap>(mStorage).key) K(std::move(entry.first));
new(&std::get<ValueWrap>(mStorage).key) V(std::move(entry.second));
std::get<bool>(mStorage) = true;
}
return *this;
}
bool used() const {
return std::get<bool>(mStorage);
}
K& key() {
assert(this->used());
return std::get<KeyWrap>(mStorage).key;
}
V& value() {
assert(this->used());
return std::get<ValueWrap>(mStorage).value;
}
};
Live demo
我还尝试自动检测一个类型是否有一些填充,但不能可靠地做到这一点,并且只能用于可以派生的类型。看看我的回答here。