SFINAE와 함께 열거 형 값 사용

Nov 19 2020

이미 SFINAE에 대해 잘 알고 있으며 전달 된 유형 (std :: enable_if 사용)에 따라 특정 템플릿을 활성화하는 데 사용할 수있는 방법에 대해 잘 알고 있습니다. 그러나 최근에 다음을 수행하려는 프로젝트에서 작업을 시작했습니다. SFINAE를 사용하는 동안 제공된 enum VALUE를 기반으로 클래스 전문화를 만듭니다. 이제 이전에이 작업을 수행했음을 고려할 때 열거 형 값을 기반으로 전문화 할 수 있다는 것을 알고 있습니다 (예 :

enum Specifier
{
   One,
   Two,
   Three
}

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

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

그러나 이제 SFINAE를 사용하여 Bar()여러 열거 형 값 에 대한 특정 전문화를 사용하고 싶습니다 . 이 같은:

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();
}

이것이 가능하다면 어떻게해야할까요?

답변

7 dfrib Nov 19 2020 at 11:51

C ++ 17 : constexpr if

C ++ 17 이상부터 다음과 같은 경우 본문에서 constexpr을 활용하는 단일 멤버 함수 오버로드 (SFINAE를 통해 존재하거나 존재하지 않는 여러 오버로드 대신)를 사용할 수 있습니다.

#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 및 std::enable_if( _t) (C ++ 14)

SFINAE 는 각 함수 선언 의 종속 이름 과 클래스 템플릿 (유형 또는 non-type) 매개 변수는 당연히 템플릿아닌 멤버 함수 의 선언에서 종속 이름이 아닙니다 .

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

위의 예에서는 std::enable_if_tC ++ 14에 도입 된 도우미 별칭 템플릿 을 사용합니다 . C ++ 11을 사용 typename std::enable_if<..>::type하는 경우 대신 사용해야 합니다.

또한 멤버 함수를 템플릿 화해야하므로 악의적 인 사용자는 (더미) 유형이 아닌 템플릿 매개 변수에 대한 기본 템플릿 인수를 재정의하도록 선택할 수 있습니다 S_.

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

따라서 std::enable_if_t각 오버로드에 대한 조건 자에 추가 AND 조건 , 즉 (S_ == S) && (... predicate as above). 다음 섹션에서 볼 수 있듯이, 이것은 비 템플릿 멤버 함수를 SFINAE 적용만을위한 템플릿으로 만드는 것을 피할 수 있기 때문에 더 이상 C ++ 20에서 문제가되지 않습니다.

오버로딩 대신 전문화를 사용하는 대안

이 질문에 대한 후속 질문 에 대한 다음 답변 에서도 보였 듯이 전문화의 템플릿 인수 목록 (부분적으로 전문화 된 클래스 템플릿에)에 SFINAE를 적용 할 수도 있습니다.

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 : 클래스 템플릿의 비 템플릿 멤버 함수는 requires-clause : s를 사용할 수 있습니다 .

C ++ 20부터는 각 오버로드에 대한 상호 배타적 제약 조건이 있는 후행 require 을 사용하여 클래스 템플릿의 템플릿이 아닌 멤버 함수를 오버로드하고 제약 할 수 있습니다 .

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