如何让 static_assert 与 SFINAE 一起玩得开心
How to make static_assert play nice with SFINAE
更新
我发布了 rebind
的工作草稿作为问题的答案。尽管我没有找到一种通用方法来防止 static_assert
破坏元功能。
基本上我想检查是否可以从其他类型 T<V, Args...>
构造模板类型 T<U, Args...>
。其中 T
和 Args...
在两种类型中是相同的。问题是,T<>
中可能有一个 static_assert
完全破坏了我的元功能。
下面是我正在尝试做的事情的粗略总结。
template<typename T>
struct fake_alloc {
using value_type = T;
};
template<typename T, typename Alloc = fake_alloc<T>>
struct fake_cont {
using value_type = T;
// comment the line below out, and it compiles, how can I get it to compile without commenting this out???
static_assert(std::is_same<value_type, typename Alloc::value_type>::value, "must be the same type");
};
template<typename T, typename U, typename = void>
struct sample_rebind {
using type = T;
};
template<template<typename...> class Container, typename T, typename U, typename... OtherArgs>
struct sample_rebind<
Container<T, OtherArgs...>,
U,
std::enable_if_t<
std::is_constructible<
Container<T, OtherArgs...>,
Container<U, OtherArgs...>
>::value
>
>
{
using type = Container<U, OtherArgs...>;
};
static_assert(
std::is_same<
fake_cont<int, fake_alloc<int>>,
typename sample_rebind<fake_cont<int>, double>::type
>::value,
"This should pass!"
);
如您所见,期望的行为是最终的 static_assert
应该通过,但不幸的是,它甚至没有达到这一点,因为 fake_cont
中的 static_assert
是当 std::is_constructible<>
试图调用 fake_cont
的构造函数时触发。
在真正的代码中fake_cont
是libc++的std::vector
,所以我不能修改它的胆量,或者std::is_constructible
的胆量。
感谢任何解决此特定问题的建议,特别感谢任何关于 SFINAE 围绕 static_assert
的一般建议。
编辑:is_same 的第一部分应该是 fake_cont<int, fake_alloc<int>>
编辑 2:如果您注释掉 fake_cont
中的 static_assert
,它会编译 (clang 3.5)。这就是我想要的。所以我只需要一些方法来避免 fake_cont
.
中的 static_assert
namespace details {
template<class T,class=void>
struct extra_test_t: std::true_type {};
}
然后我们折叠一个额外的测试:
template<class...>struct types{using type=types;};
template<template<typename...> class Container, typename T, typename U, typename... OtherArgs>
struct sample_rebind<
Container<T, OtherArgs...>,
U,
std::enable_if_t<
details::extra_test_t< types< Container<T, OtherArgs...>, U > >::value
&& std::is_constructible<
Container<T, OtherArgs...>,
Container<U, OtherArgs...>
>::value
>
> {
using type = Container<U, OtherArgs...>;
};
然后我们编写额外的测试:
namespace details {
template<class T, class Alloc, class U>
struct extra_test_t<
types<std::vector<T,Alloc>, U>,
typename std::enable_if<
std::is_same<value_type, typename Alloc::value_type>::value
>::type
> : std::true_type {};
template<class T, class Alloc, class U>
struct extra_test_t<
types<std::vector<T,Alloc>, U>,
typename std::enable_if<
!std::is_same<value_type, typename Alloc::value_type>::value
>::type
> : std::false_type {};
}
基本上,这让我们在测试中注入 "patches" 以匹配 static_assert
.
如果我们有 is_std_container<T>
和 get_allocator<T>
,我们可以写:
namespace details {
template<template<class...>class Z,class T, class...Other, class U>
struct extra_test_t<
types<Z<T,Other...>>, U>,
typename std::enable_if<
is_std_container<Z<T,Other...>>>::value
&& std::is_same<
value_type,
typename get_allocator<Z<T,Other...>>::value_type
>::value
>::type
> : std::true_type {};
template<class T, class Alloc, class U>
struct extra_test_t<
types<std::vector<T,Alloc>, U>,
typename std::enable_if<
is_std_container<Z<T,Other...>>>::value
&& !std::is_same<
value_type,
typename get_allocator<Z<T,Other...>>::value_type
>::value
>::type
> : std::false_type {};
}
或者我们可以声明任何带有 allocator_type
的东西都可能无法反弹。
解决此问题的容器感知能力更强的方法是提取分配器类型 (::allocator_type
),并用 [=19= 的重新绑定替换容器参数列表中分配器类型的所有实例] 以某种方式 U
。这仍然很棘手,因为 std::map<int, int>
有一个 std::allocator< std::pair<const int, int> >
类型的分配器,并且不可能以通用方式区分键 int
和值 int
。
我已经完成了一份非常可靠的重新绑定初稿。它适用于所有 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
定义为 a(n) std::pair
,首先是 const
ed map-like类型的模板参数,即pair
中的first_type
。 map-like 类型的第二个模板参数与 pair
的 second_type
完全匹配。 rebind
必须更小心地处理类似地图的类型。
has_mem_rebind
使用 void_t
技巧检测 T
上成员 rebind
元函数的存在。如果 class 有 rebind
那么我们将首先推迟到 classes 实现。
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
用 U
替换所有出现的 T
,对于所有不匹配的出现,调用 rebind
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 it's rebind<T>::other
. For pointers,它只是 rebind<T>
。如果 rebind<T>::other
存在,则 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
是否是 rebind
able。那里有一个 static_assert
以防它收到模板实例化。如果命中,您可能需要 rebind
的另一个专业化
- 类地图
rebind
期望像 rebind<std::map<int, int>, std::pair<double, std::string>>
一样被调用。所以分配器被反弹到的类型与容器被反弹到的类型并不完全匹配。它对除 Key 和 Value 类型之外的所有类型执行 replace_or_rebind_if_not
,其中 if_not
是 allocator_type
。由于分配器类型不同于 key/value 对 rebind
需要修改该对第一个元素的 const
ness。它使用 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[]
rebind
s 左值引用 T
类型为保证左值引用 std::remove_reference_t<U>
.
rebind
s rvalue-ref T
types to a guaranteed rvalue-ref to std::remove_reference_t<U>
.
派生(公开)Class
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 容器中那样的 SFINAE 围绕 static_assert
的通用方法。这真的让我希望这种语言有一些更 SFINAE 友好的东西,但比概念更特别。
喜欢:
template<class T>
static_assert(CACHE_LINE_SIZE == 64, "")
struct my_struct { ... };
更新
我发布了 rebind
的工作草稿作为问题的答案。尽管我没有找到一种通用方法来防止 static_assert
破坏元功能。
基本上我想检查是否可以从其他类型 T<V, Args...>
构造模板类型 T<U, Args...>
。其中 T
和 Args...
在两种类型中是相同的。问题是,T<>
中可能有一个 static_assert
完全破坏了我的元功能。
下面是我正在尝试做的事情的粗略总结。
template<typename T>
struct fake_alloc {
using value_type = T;
};
template<typename T, typename Alloc = fake_alloc<T>>
struct fake_cont {
using value_type = T;
// comment the line below out, and it compiles, how can I get it to compile without commenting this out???
static_assert(std::is_same<value_type, typename Alloc::value_type>::value, "must be the same type");
};
template<typename T, typename U, typename = void>
struct sample_rebind {
using type = T;
};
template<template<typename...> class Container, typename T, typename U, typename... OtherArgs>
struct sample_rebind<
Container<T, OtherArgs...>,
U,
std::enable_if_t<
std::is_constructible<
Container<T, OtherArgs...>,
Container<U, OtherArgs...>
>::value
>
>
{
using type = Container<U, OtherArgs...>;
};
static_assert(
std::is_same<
fake_cont<int, fake_alloc<int>>,
typename sample_rebind<fake_cont<int>, double>::type
>::value,
"This should pass!"
);
如您所见,期望的行为是最终的 static_assert
应该通过,但不幸的是,它甚至没有达到这一点,因为 fake_cont
中的 static_assert
是当 std::is_constructible<>
试图调用 fake_cont
的构造函数时触发。
在真正的代码中fake_cont
是libc++的std::vector
,所以我不能修改它的胆量,或者std::is_constructible
的胆量。
感谢任何解决此特定问题的建议,特别感谢任何关于 SFINAE 围绕 static_assert
的一般建议。
编辑:is_same 的第一部分应该是 fake_cont<int, fake_alloc<int>>
编辑 2:如果您注释掉 fake_cont
中的 static_assert
,它会编译 (clang 3.5)。这就是我想要的。所以我只需要一些方法来避免 fake_cont
.
static_assert
namespace details {
template<class T,class=void>
struct extra_test_t: std::true_type {};
}
然后我们折叠一个额外的测试:
template<class...>struct types{using type=types;};
template<template<typename...> class Container, typename T, typename U, typename... OtherArgs>
struct sample_rebind<
Container<T, OtherArgs...>,
U,
std::enable_if_t<
details::extra_test_t< types< Container<T, OtherArgs...>, U > >::value
&& std::is_constructible<
Container<T, OtherArgs...>,
Container<U, OtherArgs...>
>::value
>
> {
using type = Container<U, OtherArgs...>;
};
然后我们编写额外的测试:
namespace details {
template<class T, class Alloc, class U>
struct extra_test_t<
types<std::vector<T,Alloc>, U>,
typename std::enable_if<
std::is_same<value_type, typename Alloc::value_type>::value
>::type
> : std::true_type {};
template<class T, class Alloc, class U>
struct extra_test_t<
types<std::vector<T,Alloc>, U>,
typename std::enable_if<
!std::is_same<value_type, typename Alloc::value_type>::value
>::type
> : std::false_type {};
}
基本上,这让我们在测试中注入 "patches" 以匹配 static_assert
.
如果我们有 is_std_container<T>
和 get_allocator<T>
,我们可以写:
namespace details {
template<template<class...>class Z,class T, class...Other, class U>
struct extra_test_t<
types<Z<T,Other...>>, U>,
typename std::enable_if<
is_std_container<Z<T,Other...>>>::value
&& std::is_same<
value_type,
typename get_allocator<Z<T,Other...>>::value_type
>::value
>::type
> : std::true_type {};
template<class T, class Alloc, class U>
struct extra_test_t<
types<std::vector<T,Alloc>, U>,
typename std::enable_if<
is_std_container<Z<T,Other...>>>::value
&& !std::is_same<
value_type,
typename get_allocator<Z<T,Other...>>::value_type
>::value
>::type
> : std::false_type {};
}
或者我们可以声明任何带有 allocator_type
的东西都可能无法反弹。
解决此问题的容器感知能力更强的方法是提取分配器类型 (::allocator_type
),并用 [=19= 的重新绑定替换容器参数列表中分配器类型的所有实例] 以某种方式 U
。这仍然很棘手,因为 std::map<int, int>
有一个 std::allocator< std::pair<const int, int> >
类型的分配器,并且不可能以通用方式区分键 int
和值 int
。
我已经完成了一份非常可靠的重新绑定初稿。它适用于所有 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
定义为 a(n)std::pair
,首先是const
ed map-like类型的模板参数,即pair
中的first_type
。 map-like 类型的第二个模板参数与pair
的second_type
完全匹配。rebind
必须更小心地处理类似地图的类型。has_mem_rebind
使用void_t
技巧检测T
上成员rebind
元函数的存在。如果 class 有rebind
那么我们将首先推迟到 classes 实现。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
用U
替换所有出现的T
,对于所有不匹配的出现,调用 rebindreplace_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 it's rebind<T>::other
. For pointers,它只是 rebind<T>
。如果 rebind<T>::other
存在,则 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
是否是rebind
able。那里有一个static_assert
以防它收到模板实例化。如果命中,您可能需要rebind
的另一个专业化
- 类地图
rebind
期望像rebind<std::map<int, int>, std::pair<double, std::string>>
一样被调用。所以分配器被反弹到的类型与容器被反弹到的类型并不完全匹配。它对除 Key 和 Value 类型之外的所有类型执行replace_or_rebind_if_not
,其中if_not
是allocator_type
。由于分配器类型不同于 key/value 对rebind
需要修改该对第一个元素的const
ness。它使用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[]
rebind
s 左值引用T
类型为保证左值引用std::remove_reference_t<U>
.rebind
s rvalue-refT
types to a guaranteed rvalue-ref tostd::remove_reference_t<U>
.
派生(公开)Class
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 容器中那样的 SFINAE 围绕 static_assert
的通用方法。这真的让我希望这种语言有一些更 SFINAE 友好的东西,但比概念更特别。
喜欢:
template<class T>
static_assert(CACHE_LINE_SIZE == 64, "")
struct my_struct { ... };