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{};

我们有两种情况:

  1. Uunsigned 类型,而 D%(sizeof(U)*8)==0
  2. Uunsigned 类型,而 D%(sizeof(U)*8)!=0

为了区分大小写,让我们有两个成员:valuedivisible。基本情况是不满足任何条件,并且用 valuedivisible 实例化结构,两者都是 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);
};