简体   繁体   English

如何在不指定枚举的情况下将SFINAE用于枚举?

[英]How to use SFINAE for enum without specifying the enum?

Using answer from Substitution failure is not an error (SFINAE) for enum I tried to write a code that would get enum value from a class, and if this enum value is not found it would have a fallback value. 使用替代失败的答案不是针对枚举的错误(SFINAE),我试图编写一个代码,该代码将从类中获取枚举值,如果找不到该枚举值,它将具有一个后备值。 As I'm beginner with templates, after couple of hours I gave up and found a "solution" using macros :( 由于我是模板的初学者,几个小时后,我放弃了,使用宏:(

Is there a way to do the same thing without macros and without copying the code for every possible enum value? 有没有办法在没有宏且没有为每个可能的枚举值复制代码的情况下做同样的事情?

This is what I came up with: 这是我想出的:

struct foo
{
    enum FooFields
    {
        enumFoo,
        enumHehe
    };
};

struct bar
{
    enum BarFields
    {
        enumHehe = 2
    };
};

#define GETENUM_DEF(testedEnum) \
template<class T> \
struct get_ ## testedEnum{\
  typedef char yes;\
  typedef yes (&no)[2];\
\
  template<int>\
  struct test2;\
\
  template<class U>\
  static int test(test2<U::testedEnum>*){return U::testedEnum;};\
  template<class U>\
  static int test(...){return -1;};\
\
  static int value(){return test<T>(0);}\
};

GETENUM_DEF(enumFoo)
GETENUM_DEF(enumHehe)

int main() {

    std::cout<<get_enumFoo<foo>::value()<<std::endl; //returns 0;
    std::cout<<get_enumFoo<bar>::value()<<std::endl; //returns -1;

    std::cout<<get_enumHehe<foo>::value()<<std::endl; //returns 1;
    std::cout<<get_enumHehe<bar>::value()<<std::endl; //returns 2;

    return 0;
}

C++ requires that you define a get_someField for every field you want to get, but you'd have to do that with or without macros. C ++要求您为每个要获取的字段定义一个get_someField ,但是无论是否使用宏,都必须这样做。

Playing with SFINAE is part of what is known as template meta-programming. 使用SFINAE是所谓的模板元编程的一部分。 What you are doing is effectively detecting whether the expression T::enumFoo is valid, and returning that value if it is, else -1 . 您正在执行的操作是有效检测表达式T::enumFoo是否有效,如果有效,则返回该值,否则返回-1

To detect whether expression is valid, we can do something like this: 要检测表达式是否有效,我们可以执行以下操作:

#include <type_traits>

// You need void_t to avoid a warning about the lhs of the comma operator 
// having no effect. C++ 17 has std::void_t
template<class...> using void_t = void;

template<class T, class = void>
struct get_enumFoo
{
    static constexpr int value = -1;
};

template<class T>
struct get_enumFoo<T, void_t<decltype(T::enumFoo)>>
{
    static constexpr int value = T::enumFoo;
};

template<class T, class = void>
struct get_enumHehe
{
    static constexpr int value = -1;
};

template<class T>
struct get_enumHehe<T, void_t<decltype(T::enumHehe)>>
{
    static constexpr int value = T::enumHehe;
};

Using it (your example): 使用它(您的示例):

#include <iostream>

int main() {
    std::cout << get_enumFoo<foo>::value << std::endl; //returns 0;
    std::cout << get_enumFoo<bar>::value << std::endl; //returns -1;

    std::cout << get_enumHehe<foo>::value << std::endl; //returns 1;
    std::cout << get_enumHehe<bar>::value << std::endl; //returns 2;
}

It works as follows: 其工作方式如下:

We define a case for SFINAE to fall back on in the case that the expression T::enumFoo is invalid: 我们为表达式T::enumFoo无效的情况定义了SFINAE的情况:

template<class T, class = void>
struct get_enumFoo
{
    static constexpr int value = -1;
};

We then define the case that sees if T::enumFoo is valid: 然后,我们定义看T::enumFoo是否有效的情况:

template<class T>
struct get_enumFoo<T, void_t<decltype(T::enumFoo)>>
{
    static constexpr int value = T::enumFoo;
};

If T::enumFoo is invalid, then the decltype(T::enumFoo) is an invalid expression, so SFINAE kicks in and we fall back to our previous case. 如果T::enumFoo无效,则decltype(T::enumFoo)是无效的表达式,因此SFINAE启动,我们回到前面的情况。

If T::enumFoo is valid, then decltype(T::enumFoo) is some type, but void_t<decltype(T::enumFoo)> is void. 如果T::enumFoo有效,则decltype(T::enumFoo)是某种类型,但是void_t<decltype(T::enumFoo)>是无效的。 So we are specializing get_enumFoo<T, void> to have the field value = T::enumFoo . 因此,我们专门使get_enumFoo<T, void>具有字段value = T::enumFoo

Try it online 在线尝试


To further reduce the boilerplate behind adding new get_field traits, you could define a base class: 为了进一步减少添加新的get_field特性的样板,可以定义一个基类:

namespace detail {
    struct get_foo_defaultValue
    {
        static constexpr int value = -1;
    };
}

Then the base case would be 那么基本情况就是

template<class T, class = void>
struct get_enumFoo
    : detail::get_foo_defaultValue
{};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM