我已经设法获得了一个相当可靠的重新绑定初稿。它适用于所有 STL 容器(除了不太常见的模板参数组合)、容器适配器和 std::integer_sequence。它可能也适用于更多的事情。但它肯定不会对所有事情都有效。
主要的问题是让类似地图的类型像 Yakk 预测的那样工作,但一个小的类型特征有助于解决这个问题。
继续代码...
void_t
template<class...>
using void_t = void;
Walter E. Brown 的这个小技巧使实现类型特征变得更加容易。
类型特征
template<class T, class = void>
struct is_map_like : std::false_type {};
template<template<class...> class C, class First, class Second, class... Others>
struct is_map_like<C<First, Second, Others...>,
std::enable_if_t<std::is_same<typename C<First, Second, Others...>::value_type::first_type,
std::add_const_t<First>>{} &&
std::is_same<typename C<First, Second, Others...>::value_type::second_type,
Second>{}>>
: std::true_type {};
template<class T, class U, class = void>
struct has_mem_rebind : std::false_type {};
template<class T, class U>
struct has_mem_rebind<T, U, void_t<typename T::template rebind<U>>> : std::true_type {};
template<class T>
struct is_template_instantiation : std::false_type {};
template<template<class...> class C, class... Others>
struct is_template_instantiation<C<Others...>> : std::true_type {};
-
is_map_like 使用了这样一个事实,即 STL 中的类映射类型都将 value_type 定义为一个(n)std::pair,其中 consted 类映射类型的第一个模板参数是 @987654330 @在pair。 map-like 类型的第二个模板参数与pair 的second_type 完全匹配。 rebind 必须更加小心地处理类似地图的类型。
-
has_mem_rebind 使用void_t 技巧检测T 上的成员rebind 元函数的存在。如果一个类有rebind,那么我们将首先遵循类的实现。
-
is_template_instantiation 检测 T 类型是否为模板实例化。这更适合调试。
助手类型列表
template<class... Types>
struct pack
{
template<class T, class U>
using replace = pack<
std::conditional_t<
std::is_same<Types, T>{},
U,
Types
>...
>;
template<class T, class U>
using replace_or_rebind = pack<
std::conditional_t<
std::is_same<Types, T>{},
U,
typename rebind<Types, U>::type
>...
>;
template<class Not, class T, class U>
using replace_or_rebind_if_not = pack<
std::conditional_t<
std::is_same<Types, Not>{},
Types,
std::conditional_t<
std::is_same<Types, T>{},
U,
typename rebind<Types, U>::type
>
>...
>;
template<class T>
using push_front = pack<T, Types...>;
};
这处理一些简单的列表,比如类型的操作
-
replace 以非递归方式将所有出现的 T 替换为 U。
-
replace_or_rebind 将所有出现的T 替换为U,对于所有不匹配的出现,调用重新绑定
-
replace_or_rebind_if_not 与 replace_or_rebind 相同,但会跳过与 Not 匹配的任何元素
-
push_front 只是将一个元素推到类型列表的前面
呼叫成员重新绑定
// has member rebind implemented as alias
template<class T, class U, class = void>
struct do_mem_rebind
{
using type = typename T::template rebind<U>;
};
// has member rebind implemented as rebind::other
template<class T, class U>
struct do_mem_rebind<T, U, void_t<typename T::template rebind<U>::other>>
{
using type = typename T::template rebind<U>::other;
};
事实证明,根据标准实现成员rebind 有两种不同的有效方法。对于allocators,它是rebind<T>::other。对于pointers,它只是rebind<T>。 do_mem_rebind 的这个实现与 rebind<T>::other 如果存在,否则将回退到更简单的 rebind<T>。
拆包
template<template<class...> class C, class Pack>
struct unpack;
template<template<class...> class C, class... Args>
struct unpack<C, pack<Args...>> { using type = C<Args...>; };
template<template<class...> class C, class Pack>
using unpack_t = typename unpack<C, Pack>::type;
这需要pack,提取它包含的类型,并将它们放入其他模板C。
重新绑定实施
好东西。
template<class T, class U, bool = is_map_like<T>{}, bool = std::is_lvalue_reference<T>{}, bool = std::is_rvalue_reference<T>{}, bool = has_mem_rebind<T, U>{}>
struct rebind_impl
{
static_assert(!is_template_instantiation<T>{}, "Sorry. Rebind is not completely implemented.");
using type = T;
};
// map-like container
template<class U, template<class...> class C, class First, class Second, class... Others>
class rebind_impl<C<First, Second, Others...>, U, true, false, false, false>
{
using container_type = C<First, Second, Others...>;
using value_type = typename container_type::value_type;
using old_alloc_type = typename container_type::allocator_type;
using other_replaced = typename pack<Others...>::template replace_or_rebind_if_not<old_alloc_type, First, typename U::first_type>;
using new_alloc_type = typename std::allocator_traits<old_alloc_type>::template rebind_alloc<std::pair<std::add_const_t<typename U::first_type>, typename U::second_type>>;
using replaced = typename other_replaced::template replace<old_alloc_type, new_alloc_type>;
using tail = typename replaced::template push_front<typename U::second_type>;
public:
using type = unpack_t<C, typename tail::template push_front<typename U::first_type>>;
};
// has member rebind
template<class T, class U>
struct rebind_impl<T, U, false, false, false, true>
{
using type = typename do_mem_rebind<T, U>::type;
};
// has nothing, try rebind anyway
template<template<class...> class C, class T, class U, class... Others>
class rebind_impl<C<T, Others...>, U, false, false, false, false>
{
using tail = typename pack<Others...>::template replace_or_rebind<T, U>;
public:
using type = unpack_t<C, typename tail::template push_front<U>>;
};
// has nothing, try rebind anyway, including casting NonType template parameters
template<class T, template<class, T...> class C, class U, T FirstNonType, T... Others>
struct rebind_impl<C<T, FirstNonType, Others...>, U, false, false, false, false>
{
using type = C<U, U(FirstNonType), U(Others)...>;
};
// array takes a non-type parameter parameters
template<class T, class U, std::size_t Size>
struct rebind_impl<std::array<T, Size>, U, false, false, false, false>
{
using type = std::array<U, Size>;
};
// pointer
template<class T, class U>
struct rebind_impl<T*, U, false, false, false, false>
{
using type = typename std::pointer_traits<T*>::template rebind<U>;
};
// c-array
template<class T, std::size_t Size, class U>
struct rebind_impl<T[Size], U, false, false, false, false>
{
using type = U[Size];
};
// c-array2
template<class T, class U>
struct rebind_impl<T[], U, false, false, false, false>
{
using type = U[];
};
// lvalue ref
template<class T, class U>
struct rebind_impl<T, U, false, true, false, false>
{
using type = std::add_lvalue_reference_t<std::remove_reference_t<U>>;
};
// rvalue ref
template<class T, class U>
struct rebind_impl<T, U, false, false, true, false>
{
using type = std::add_rvalue_reference_t<std::remove_reference_t<U>>;
};
-
rebind 的失败案例是简单地保持类型不变。这允许调用rebind<Types, double>...,而不必担心Types 中的每个Type 是否为rebindable。那里有一个static_assert,以防它接收到模板实例化。如果遇到这种情况,您可能需要rebind 的另一个专业化
- 类似于地图的
rebind 期望像rebind<std::map<int, int>, std::pair<double, std::string>> 一样被调用。所以分配器被反弹到的类型与容器被反弹到的类型不完全匹配。它对除键和值类型之外的所有类型执行replace_or_rebind_if_not,if_not 是allocator_type。由于分配器类型与键/值对不同,rebind 需要修改该对的第一个元素的constness。它使用std::allocator_traits 重新绑定分配器,因为所有分配器都必须可以通过std::allocator_traits 重新绑定。
- 如果
T 有一个成员rebind,请使用它。
- 如果
T 没有成员rebind,则replace_or_rebind 模板C 中与C 的第一个模板参数匹配的所有参数。
- 如果
T 有一个类型参数,以及一堆类型与该参数匹配的非类型模板参数。尝试将所有这些非类型参数重铸为U。这就是 std::integer_sequence 工作的原因。
-
std::array 需要一个特殊情况,因为它需要一个非类型模板参数来给出它的大小,并且该模板参数应该单独保留。
- 这种情况允许将指针重新绑定到其他指针类型。它使用
std::pointer_traits 的rebind 来完成此操作。
- 让
rebind 处理大小的 c 数组,例如:T[5]
- 让
rebind 处理没有大小的 c 数组 例如:T[]
-
rebinds lvalue-ref T 类型为保证 lvalue-ref 到 std::remove_reference_t<U>。
-
rebinds rvalue-ref T 类型为保证 rvalue-ref 到 std::remove_reference_t<U>。
派生(公开)类
template<class T, class U>
struct rebind : details::rebind_impl<T, U> {};
template<class T, class U>
using rebind_t = typename rebind<T, U>::type;
返回 SFINAE 和 static_assert
经过多次谷歌搜索,似乎没有像 libc++ 的 STL 容器中那样在 static_asserts 周围找到 SFINAE 的通用方法。这真的让我希望该语言有一些对 SFINAE 更友好,但比概念更临时的东西。
喜欢:
template<class T>
static_assert(CACHE_LINE_SIZE == 64, "")
struct my_struct { ... };