SFINAE и std :: enable_if в фиктивных параметрах шаблона для выбора структуры на основе двух критериев - PullRequest
0 голосов
/ 08 апреля 2020

Допустим, мы хотим, чтобы шаблонная структура part вызывалась part<U,D>, где D имеет тип U:

template<typename U, U D>
struct part{};

Мы в двух случаях:

  1. U относится к типу unsigned, а D%(sizeof(U)*8)==0
  2. U относится к типу unsigned и D%(sizeof(U)*8)!=0

Допустимо В случае с guish делаются два члена: value и divisible. Базовый случай - условие не выполняется, и для экземпляра struct используются value и divisible, оба из которых false. Если выполнены условия 1), у меня должно быть value=true, divisible= false, а если выполнены условия 2), у меня должно быть value=true, divisible= true. Забегая вперед, скажем также, что если выполнены 1) или 2), у меня также есть type в структуре.

Я настроил вспомогательную структуру choice для использования в std::enable_if и выбрал соответствующий случай через фиктивный аргумент шаблона. Код следующий:

// 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 разрешает специализации. Но швы это всегда выбирают базовый вариант. Почему?

1 Ответ

3 голосов
/ 08 апреля 2020

Это не то, как работает трюк SFINAE. Вам нужно присвоить std::enable_if_t тот же тип, что и для второго аргумента, который вы определили в качестве аргумента по умолчанию, так что если std::enable_it_t не вызывает сбоя подстановки, частичная специализация совпадает со специализацией шаблона, которая явно не указывает параметр по умолчанию.

Поэтому вы хотите использовать choice<0> везде вместо choice<1>, и поскольку вы используете его везде, вы действительно можете использовать void вместо choice<...> везде, а с void вы также можете сбросить Второй аргумент для std::enable_if_t, потому что он по умолчанию равен этому.

Тогда, наконец, вам не нужны два параметра enabler, просто соедините условия с логическими операциями:

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

Подход SFINAE необходим только для std::is_unsigned_v, поскольку выражение (D%(sizeof(U)*8) == 0) может быть некорректно сформировано, если D имеет тип, который не является целым числом без знака.

Также, начиная с 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;
  }();
};

, чтобы избежать p полная специализация, даже если 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);
};
...