Using enum values in combination with SFINAE

放肆的年华 提交于 2020-12-05 19:15:33

问题


I am already familiar with SFINAE and how it can be used to enable specific templates based on the passed type (by using std::enable_if). However I have recently started working on a project where I'd like to do the following: create a class specialization based on the provided enum VALUE while using SFINAE. Now, I know that it's possible to make specializations based on an enum value considering I've done this before (like so):

enum Specifier
{
   One,
   Two,
   Three
}

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

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

However now I'd like to use SFINAE to use a specific specialization of Bar() for multiple enum values. Something like this:

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

Any idea if this is possible, and if so, how would I go about doing this?


回答1:


C++17: constexpr if

From C++17 and onwards, you could use a single member function overload (instead of several overloads present or not present via SFINAE) whose body leverages constexpr if:

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

You can likewise use SFINAE with the requirement that your non-template member functions need to be made member function templates with a dummy template parameter, as SFINAE needs to be applied to a dependent name in each function declaration, and a class template (type or non-type) parameter is naturally not a dependent name in the declaration of a non-template member function:

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

Note that the example above uses the helper alias template std::enable_if_t that was introduced in C++14. If you are using C++11, you will need to use typename std::enable_if<..>::type instead.

Moreover note that as we have to templetize the member functions an abusive user could choose to override the default template argument for the (dummy) non-type template parameter S_:

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

So we may want to add an additional AND condition to the std::enable_if_t predicate for each overload, namely (S_ == S) && (... predicate as above). As we shall see in the section that follows, this is no longer an issue in C++20, as we can avoid making non-template member functions into templates solely for applying SFINAE.

Alternative using specializations rather than overloading

As I've also shown in the following answer to a follow up question to this question, you may also apply SFINAE in the template argument list (to the class template being partially specialized) of a specialization:

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: non-template member functions of class templates may use requires-clause:s

As of C++20, you can overload and constrain a non-template member function of a class template using a trailing requires-clause with mutual exclusive constraints for each overloaded:

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


来源:https://stackoverflow.com/questions/64910940/using-enum-values-in-combination-with-sfinae

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!