c++ 如何使用`std::is_enum`和未命名的`enum`?

b4wnujal  于 2023-07-01  发布在  其他
关注(0)|答案(4)|浏览(175)

标题是非常自我解释。以下是我的情况:

#include <type_traits>

class C1{
    enum{
        c1 = 3
    }
}

class C2{
    enum{
        c2 = 10
    }
}

template<class C>
class C3{
    void do_this();
    void do_that();

    void foo(){
        if constexpr(std::is_enum<C::c1>::value){
            do_this();
        }
        if constexpr(std::is_enum<C::c2>::value){
            do_that();
        }
    }
}

如果我试着编译它,我会得到错误

error: type/value mismatch at argument 1 in template parameter list for ‘template<class _Tp> struct std::is_enum’
note: expected a type, got ‘typename C::c1’

error: type/value mismatch at argument 1 in template parameter list for ‘template<class _Tp> struct std::is_enum’
note: expected a type, got ‘typename C::c2’

所以我的问题是:是否可以将std::is_enum与未命名的enum s一起使用?

ego6inou

ego6inou1#

完全可以将std::is_enum与未命名的枚举一起使用。
在当前的实现中,只有两个问题需要解决:

  • std::is_enum<C::c1>::value-C::c1是数据成员,但std::is_enum需要类型模板参数。

=>这里需要使用decltype(...),例如std::is_enum<decltype(C::c1)>::value

  • 如果C没有名为c1的成员,则C::c1将导致替换失败。

解决这个问题的唯一方法是在替换失败不会导致程序格式不正确的情况下执行此操作。
因此,您可以使用SFINAE或(如果C20可用)requires-expression
在C
20中,这是可行的:
godbolt example

template<class T>
concept is_enum_value = std::is_enum_v<T>;

template<class C>
struct C3 {
    void foo() {
        if constexpr(requires { { C::c1 } -> is_enum_value; }) {
            do_this();
        }
        if constexpr(requires { { C::c2 } -> is_enum_value; }) {
            do_that();
        }
    }

    // ...
};

如果C::c1C::c2互斥(即每个类C都有c1c2,或者两者都没有-但永远不会同时有c1c2),您可以考虑使用尾随的requires-clauses来重载foo
godbolt example

template<class C>
struct C3 {
    void foo() requires requires { { C::c1 } -> is_enum_value; } {
        // do_this implementation
    }
    void foo() requires requires { { C::c2 } -> is_enum_value; } {
        // do_that implementation
    }
};

如果C++20不可用,则需要使用SFINAE,这有点麻烦:
godbolt example

template<class T, class = void>
constexpr bool has_c1 = false;
template<class T>
constexpr bool has_c1<T, std::enable_if_t<std::is_enum_v<decltype(T::c1)>>> = true;

template<class T, class = void>
constexpr bool has_c2 = false;
template<class T>
constexpr bool has_c2<T, std::enable_if_t<std::is_enum_v<decltype(T::c2)>>> = true; 

template<class C>
struct C3 {
    void foo() {
        if constexpr(has_c1<C>) {
            do_this();
        }
        if constexpr(has_c2<C>) {
            do_that();
        }
    }

    // ...
};
lnvxswe2

lnvxswe22#

C++11使用SFINAE

您可以使用decltype获取与c1c2关联的类型,然后使用 SFINAE,如下所示。C++11 Demo

struct C1{
    enum{
        c1 = 3
    };
};

struct C2{
    enum{
        c2 = 10
    };
};

template<class C>
class C3{
    void do_this(){std::cout << "do this called" << std::endl;}
    void do_that(){std::cout << "do that called " << std::endl;}
public:
    //overload for calling do_this
    template<typename T = C,typename std::enable_if<std::is_same<T, C1>::value, bool>::type = std::is_enum<decltype(T::c1)>::value >void foo()
    {
        do_this();
    }
    //overload for calling do_that
    template<typename T = C,typename std::enable_if<std::is_same<T, C2>::value, bool>::type = std::is_enum<decltype(T::c2)>::value >void foo()
    {
        do_that();
    }
    //overload when none of the conditions are satisfied
    template<typename... T>
    void foo(T...)
    {
        std::cout <<"ellipsis called " << std::endl;
    }
     
};
int main()
{
    C3<C1> c1;
    c1.foo();     //calls do_this() using #1

    C3<C2> c2;
    c2.foo();     //calls do_that() using #2

    C3<int> c3;
    c3.foo();    //calls the ellipsis version
    
}

另请参阅使用std::enable_if_tstd::is_same_vstd::is_enum_vC++ 17 demo版本。

C++20使用概念
struct C1{
    enum{
        c1 = 3
    };
};
template<typename T>
concept enumC1 = std::is_same_v<T, C1>;

struct C2{
    enum{
        c2 = 10
    };
};
template<typename T>
concept enumC2 = std::is_same_v<T, C2>;

template<class C>
class C3{
    void do_this(){std::cout << "do this called" << std::endl;}
    void do_that(){std::cout << "do that called " << std::endl;}
public:
    //overload when none of the conditions are satisfied
    template<typename T = C>
    void foo()
    {
        std::cout <<"general called " << std::endl;
    }
    //overload for calling do_this
    template<enumC1 T = C>void foo()
    {
        do_this();
    }
    //overload for calling do_that
    template<enumC2 T = C>void foo()
    {
        do_that();
    }
    
     
};
int main()
{
    C3<C1> c1;
    c1.foo();     //calls do_this()

    C3<C2> c2;
    c2.foo();     //calls do_that()

    C3<int> c3;
    c3.foo();    //calls the general version
    
}

C++ 20 demo

41zrol4v

41zrol4v3#

你可能应该给它们起相同的名字,这样模板就可以同时使用它们:

#include <cstdio>
#include <type_traits>

struct C1 {
    enum { c = 3 };
};

struct C2 {
    static constexpr int c = 10;
};

template <class C>
void foo() {
    if constexpr (std::is_enum_v<decltype(C::c)>) {
        std::puts("::c is an enum");
    } else {
        std::puts("::c is not an enum");
    }
}

int main() {
    foo<C1>();
    foo<C2>();
}
ff29svar

ff29svar4#

也有很好的答案,但我会给予C++20的另一个变体:

if constexpr(requires { requires std::is_enum_v<decltype((C::c1))>; }) { /*...*/ }

这使用了一个嵌套的需求,而不是像@Turtlefight的答案中那样使用了一个带有类型约束的复合需求。如果替换到表达式中导致无效的类型或表达式,则嵌套要求失败,如果表达式不是计算为truebool类型的常量表达式,则嵌套要求也失败。但这在功能上应该是相同的。
decltype中的附加括号是必要的,另一个答案中使用的复合语句也隐含地使用decltype来确定表达式的类型。
如果没有它,decltype将为枚举类型的数据成员(而不是枚举器的名称)生成非引用类型并成功,例如。

class C2{
    static enum {
        c2 = 10
    } c1;
}

将成功进行c1c2的测试,而不是仅成功进行c2的测试。

相关问题