SFINAE 和 std::enable_if 虚拟模板参数到 select 结构基于两个标准
SFINAE and std::enable_if in dummy template parameters to select struct based on two criteria
假设我们希望 part<U,D>
调用模板结构 part
,其中 D
的类型为 U
:
template<typename U, U D>
struct part{};
我们有两种情况:
U
是 unsigned
类型,而 D%(sizeof(U)*8)==0
U
是 unsigned
类型,而 D%(sizeof(U)*8)!=0
为了区分大小写,让我们有两个成员:value
和 divisible
。基本情况是不满足任何条件,并且用 value
和 divisible
实例化结构,两者都是 false
。如果满足条件 1),我应该有 value=true, divisible= false
,如果满足条件 2),我应该有 value=true, divisible= true
。展望未来,我们还可以说,如果满足 1) 或 2),我在结构中也有一个 type
。
我设置了一个辅助结构 choice
以通过虚拟模板参数在 std::enable_if
和 select 适当的情况下使用。代码如下:
// to be used in dummy template arguments
template<size_t D>
struct choice{};
// Base case
template<typename U, U D, typename E1 = choice<0>, typename E2 = choice<0> >
struct part{
static constexpr bool value = false;
static constexpr bool divisible = false;
};
// Specialisation 1 <U, D, choice<1>, choice<0> >
template<typename U, U D>
struct part<U,D,std::enable_if_t<std::is_unsigned<U>::value, choice<1> >, std::enable_if_t<!(D%(sizeof(U)*8)), choice<0> > >{
static constexpr bool value = true;
static constexpr bool divisible = false;
typedef U type;
};
// Specialisation 2 <U, D, choice<1>, choice<1> >
template<typename U, U D>
struct part<U,D,std::enable_if_t<std::is_unsigned<U>::value, choice<1> >, std::enable_if_t<(D%(sizeof(U)*8)), choice<1> > >{
static constexpr bool value = true;
static constexpr bool divisible = true;
typedef U type;
};
编译通过。伟大的。让我们检查一下我们用简单的打印做了什么:
std::cout << "Should get 0,0 "<< std::endl;
std::cout << part<int,5>::value << std::endl;
std::cout << part<int,5>::divisible << std::endl;
std::cout << "Should get 1,0 "<< std::endl;
std::cout << part<uint16_t,5>::value << std::endl;
std::cout << part<uint16_t,5>::divisible << std::endl;
std::cout << "Should get 1,1 "<< std::endl;
std::cout << part<uint16_t,16>::value << std::endl;
std::cout << part<uint16_t,16>::divisible << std::endl;
std::cout << "Should get 1,0 "<< std::endl;
std::cout << part<uint16_t,30>::value << std::endl;
std::cout << part<uint16_t,30>::divisible << std::endl;
std::cout << "Should get 1,1 "<< std::endl;
std::cout << part<uint16_t,32>::value << std::endl;
std::cout << part<uint16_t,32>::divisible << std::endl;
// RESULTS ON TERMINAL ARE:
//
// Should get 0,0
// 0
// 0
// Should get 1,0
// 0
// 0
// Should get 1,1
// 0
// 0
// Should get 1,0
// 0
// 0
// Should get 1,1
// 0
// 0
所以根本不是我想要的。好的,让我们试试:
std::cout << std::is_same<part<uint16_t,5>::type, uint16_t> << std::endl;
// COMPILATION ERROR:
src/tests/test1.cpp(55): error: class "part<uint16_t={unsigned short}, (uint16_t={unsigned short})5U, choice<0UL>, choice<0UL>>" has no member "type"
std::cout << std::is_same<part<uint16_t,5>::type, uint16_t> << std::endl;
我哪里错了?代码编译后 SFINAE 正在解析专业化。但它总是 select 基本情况。为什么?
这不是 SFINAE 技巧的工作原理。您需要为 std::enable_if_t
提供与定义为默认参数的第二个参数相同的类型,以便如果 std::enable_it_t
不会导致替换失败,则部分特化与未明确指定的模板特化相匹配默认参数。
因此你想在任何地方使用 choice<0>
而不是 choice<1>
并且因为你在任何地方都使用它,所以你真的可以在任何地方使用 void
而不是 choice<...>
并且 void
你也可以把第二个参数丢给 std::enable_if_t
因为它是默认的。
最后,你不需要两个启动器参数,只需将条件与逻辑操作连接起来:
// Base case
template<typename U, U D, typename = void>
struct part{
static constexpr bool value = false;
static constexpr bool divisible = false;
};
// Specialisation 1 <U, D>
template<typename U, U D>
struct part<U,D,std::enable_if_t<std::is_unsigned_v<U> && !(D%(sizeof(U)*8))>{
static constexpr bool value = true;
static constexpr bool divisible = false;
typedef U type;
};
// Specialisation 2
template<typename U, U D>
struct part<U,D,std::enable_if_t<std::is_unsigned_v<U> && (D%(sizeof(U)*8))>{
static constexpr bool value = true;
static constexpr bool divisible = true;
typedef U type;
};
另请注意(根据您展示的内容)您不需要最后的专业化:
// Base case
template<typename U, U D, typename = void>
struct part{
static constexpr bool value = false;
static constexpr bool divisible = false;
};
// Specialisation 1 <U, D>
template<typename U, U D>
struct part<U,D,std::enable_if_t<std::is_unsigned_v<U>> {
static constexpr bool value = true;
static constexpr bool divisible = (D%(sizeof(U)*8) == 0);
typedef U type;
};
只有 std::is_unsigned_v
才需要 SFINAE 方法,因为如果 D
的类型不是无符号整数,则表达式 (D%(sizeof(U)*8) == 0)
可能格式不正确。
此外,从 C++17 开始,您可以:
template<typename U, U D>
struct part{
static constexpr bool value = std::is_unsigned_v<U>;
static constexpr bool divisible = []{
if constexpr(value)
return D%(sizeof(U)*8) == 0;
else
return false;
}();
};
完全避免偏特化,即使 U
可能是 D%(sizeof(U)*8)
格式错误的类型。
如果您不打算支持 D%(sizeof(U)*8)
格式错误的类型,那么只需使用:
template<typename U, U D>
struct part{
static constexpr bool value = std::is_unsigned_v<U>;
static constexpr bool divisible = value && (D%(sizeof(U)*8) == 0);
};
假设我们希望 part<U,D>
调用模板结构 part
,其中 D
的类型为 U
:
template<typename U, U D>
struct part{};
我们有两种情况:
U
是unsigned
类型,而D%(sizeof(U)*8)==0
U
是unsigned
类型,而D%(sizeof(U)*8)!=0
为了区分大小写,让我们有两个成员:value
和 divisible
。基本情况是不满足任何条件,并且用 value
和 divisible
实例化结构,两者都是 false
。如果满足条件 1),我应该有 value=true, divisible= false
,如果满足条件 2),我应该有 value=true, divisible= true
。展望未来,我们还可以说,如果满足 1) 或 2),我在结构中也有一个 type
。
我设置了一个辅助结构 choice
以通过虚拟模板参数在 std::enable_if
和 select 适当的情况下使用。代码如下:
// to be used in dummy template arguments
template<size_t D>
struct choice{};
// Base case
template<typename U, U D, typename E1 = choice<0>, typename E2 = choice<0> >
struct part{
static constexpr bool value = false;
static constexpr bool divisible = false;
};
// Specialisation 1 <U, D, choice<1>, choice<0> >
template<typename U, U D>
struct part<U,D,std::enable_if_t<std::is_unsigned<U>::value, choice<1> >, std::enable_if_t<!(D%(sizeof(U)*8)), choice<0> > >{
static constexpr bool value = true;
static constexpr bool divisible = false;
typedef U type;
};
// Specialisation 2 <U, D, choice<1>, choice<1> >
template<typename U, U D>
struct part<U,D,std::enable_if_t<std::is_unsigned<U>::value, choice<1> >, std::enable_if_t<(D%(sizeof(U)*8)), choice<1> > >{
static constexpr bool value = true;
static constexpr bool divisible = true;
typedef U type;
};
编译通过。伟大的。让我们检查一下我们用简单的打印做了什么:
std::cout << "Should get 0,0 "<< std::endl;
std::cout << part<int,5>::value << std::endl;
std::cout << part<int,5>::divisible << std::endl;
std::cout << "Should get 1,0 "<< std::endl;
std::cout << part<uint16_t,5>::value << std::endl;
std::cout << part<uint16_t,5>::divisible << std::endl;
std::cout << "Should get 1,1 "<< std::endl;
std::cout << part<uint16_t,16>::value << std::endl;
std::cout << part<uint16_t,16>::divisible << std::endl;
std::cout << "Should get 1,0 "<< std::endl;
std::cout << part<uint16_t,30>::value << std::endl;
std::cout << part<uint16_t,30>::divisible << std::endl;
std::cout << "Should get 1,1 "<< std::endl;
std::cout << part<uint16_t,32>::value << std::endl;
std::cout << part<uint16_t,32>::divisible << std::endl;
// RESULTS ON TERMINAL ARE:
//
// Should get 0,0
// 0
// 0
// Should get 1,0
// 0
// 0
// Should get 1,1
// 0
// 0
// Should get 1,0
// 0
// 0
// Should get 1,1
// 0
// 0
所以根本不是我想要的。好的,让我们试试:
std::cout << std::is_same<part<uint16_t,5>::type, uint16_t> << std::endl;
// COMPILATION ERROR:
src/tests/test1.cpp(55): error: class "part<uint16_t={unsigned short}, (uint16_t={unsigned short})5U, choice<0UL>, choice<0UL>>" has no member "type"
std::cout << std::is_same<part<uint16_t,5>::type, uint16_t> << std::endl;
我哪里错了?代码编译后 SFINAE 正在解析专业化。但它总是 select 基本情况。为什么?
这不是 SFINAE 技巧的工作原理。您需要为 std::enable_if_t
提供与定义为默认参数的第二个参数相同的类型,以便如果 std::enable_it_t
不会导致替换失败,则部分特化与未明确指定的模板特化相匹配默认参数。
因此你想在任何地方使用 choice<0>
而不是 choice<1>
并且因为你在任何地方都使用它,所以你真的可以在任何地方使用 void
而不是 choice<...>
并且 void
你也可以把第二个参数丢给 std::enable_if_t
因为它是默认的。
最后,你不需要两个启动器参数,只需将条件与逻辑操作连接起来:
// Base case
template<typename U, U D, typename = void>
struct part{
static constexpr bool value = false;
static constexpr bool divisible = false;
};
// Specialisation 1 <U, D>
template<typename U, U D>
struct part<U,D,std::enable_if_t<std::is_unsigned_v<U> && !(D%(sizeof(U)*8))>{
static constexpr bool value = true;
static constexpr bool divisible = false;
typedef U type;
};
// Specialisation 2
template<typename U, U D>
struct part<U,D,std::enable_if_t<std::is_unsigned_v<U> && (D%(sizeof(U)*8))>{
static constexpr bool value = true;
static constexpr bool divisible = true;
typedef U type;
};
另请注意(根据您展示的内容)您不需要最后的专业化:
// Base case
template<typename U, U D, typename = void>
struct part{
static constexpr bool value = false;
static constexpr bool divisible = false;
};
// Specialisation 1 <U, D>
template<typename U, U D>
struct part<U,D,std::enable_if_t<std::is_unsigned_v<U>> {
static constexpr bool value = true;
static constexpr bool divisible = (D%(sizeof(U)*8) == 0);
typedef U type;
};
只有 std::is_unsigned_v
才需要 SFINAE 方法,因为如果 D
的类型不是无符号整数,则表达式 (D%(sizeof(U)*8) == 0)
可能格式不正确。
此外,从 C++17 开始,您可以:
template<typename U, U D>
struct part{
static constexpr bool value = std::is_unsigned_v<U>;
static constexpr bool divisible = []{
if constexpr(value)
return D%(sizeof(U)*8) == 0;
else
return false;
}();
};
完全避免偏特化,即使 U
可能是 D%(sizeof(U)*8)
格式错误的类型。
如果您不打算支持 D%(sizeof(U)*8)
格式错误的类型,那么只需使用:
template<typename U, U D>
struct part{
static constexpr bool value = std::is_unsigned_v<U>;
static constexpr bool divisible = value && (D%(sizeof(U)*8) == 0);
};