Usando valores enum em combinação com SFINAE

Nov 19 2020

Já estou familiarizado com SFINAE e como ele pode ser usado para habilitar modelos específicos com base no tipo passado (usando std :: enable_if). No entanto, recentemente comecei a trabalhar em um projeto no qual gostaria de fazer o seguinte: criar uma especialização de classe com base no enum fornecido VALUE ao usar SFINAE. Agora, eu sei que é possível fazer especializações com base em um valor enum, considerando que já fiz isso antes (assim):

enum Specifier
{
   One,
   Two,
   Three
}

template <Specifier>
class Foo
{
public:
   void Bar();
}

template<>
void Foo<Specifier::One>::Bar()
{
}

No entanto, agora gostaria de usar SFINAE para usar uma especialização específica de Bar()para vários valores de enum. Algo assim:

template <Specifier Type>
class Foo
{
public:
   template <typename std::enable_if<Type == Specifier::Two || Type == Specifier::One, void>::type> 
   void Bar();

   template <typename std::enable_if<Type == Specifier::Three, void>::type> 
   void Bar();
}

Alguma ideia se isso é possível e, em caso afirmativo, como faria para fazer isso?

Respostas

7 dfrib Nov 19 2020 at 11:51

C ++ 17: constexpr se

Do C ++ 17 em diante, você poderia usar uma única sobrecarga de função de membro (em vez de várias sobrecargas presentes ou não presentes via SFINAE) cujo corpo alavanca constexpr se:

#include <iostream>

enum class Specifier { One, Two, Three };

template <Specifier S> class Foo {
public:
  static constexpr int bar() {
    if constexpr ((S == Specifier::One) || (S == Specifier::Two)) {
      return 12;
    } else if constexpr (S == Specifier::Three) {
      return 3;
    }
  }
};

int main() {
  std::cout << Foo<Specifier::One>::bar() << "\n" // 12
            << Foo<Specifier::Two>::bar() << "\n" // 12
            << Foo<Specifier::Three>::bar();      // 3
}

C ++ 11: SFINAE e std::enable_if( _t) (C ++ 14)

Da mesma forma, você pode usar SFINAE com o requisito de que suas funções de membro não-modelo precisam ser feitas de modelos de função de membro com um parâmetro de modelo fictício, já que SFINAE precisa ser aplicado a um nome dependente em cada declaração de função e um modelo de classe (tipo ou parâmetro não tipo) é naturalmente não um nome dependente na declaração de uma função de membro não modelo :

template <Specifier S> class Foo {
public:
  template <Specifier S_ = S,
            std::enable_if_t<(S_ == Specifier::One) || (S_ == Specifier::Two)>
                * = nullptr>
  static constexpr int bar() {
    return 12;
  }

  template <Specifier S_ = S,
            std::enable_if_t<(S_ == Specifier::Three)> * = nullptr>
  static constexpr int bar() {
    return 3;
  }
};

Observe que o exemplo acima usa o modelo de alias auxiliar std::enable_if_tque foi introduzido no C ++ 14. Se estiver usando C ++ 11, você precisará usar em seu typename std::enable_if<..>::typelugar.

Além disso, observe que, como temos que modelar as funções de membro, um usuário abusivo pode escolher substituir o argumento do modelo padrão para o parâmetro de modelo sem tipo (fictício) S_:

Foo<Specifier::One>::bar<Specifier::Three>();  // 3

Portanto, podemos querer adicionar uma condição AND adicional ao std::enable_if_tpredicado para cada sobrecarga, a saber (S_ == S) && (... predicate as above). Como veremos na seção a seguir, isso não é mais um problema no C ++ 20, pois podemos evitar transformar funções-membro não-template em templates apenas para aplicar SFINAE.

Alternativa usando especializações em vez de sobrecarregar

Como também mostrei na seguinte resposta a uma pergunta de acompanhamento a esta pergunta, você também pode aplicar SFINAE na lista de argumentos do modelo (para o modelo de classe ser parcialmente especializado) de uma especialização:

template <Specifier, typename = void> struct Foo {
  static constexpr int bar() { return 1; }  // default
};

template <Specifier S>
struct Foo<S,
           std::enable_if_t<(S == Specifier::One) || (S == Specifier::Two)>> {
  static constexpr int bar() { return 12; }
};

C ++ 20: funções de membro não-modelo de modelos de classe podem usar requer-cláusula : s

A partir do C ++ 20, você pode sobrecarregar e restringir uma função de membro não modelo de um modelo de classe usando uma cláusula de requerimentos à direita com restrições mútuas exclusivas para cada sobrecarregado:

template <Specifier S> class Foo {
public:
  static constexpr int bar() requires((S == Specifier::One) ||
                                      (S == Specifier::Two)) {
    return 12;
  }

  static constexpr int bar() requires(S == Specifier::Three) { return 3; }
};