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.
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.
Playing with SFINAE is part of what is known as template meta-programming. What you are doing is effectively detecting whether the expression T::enumFoo
is valid, and returning that value if it is, else -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:
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:
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.
If T::enumFoo
is valid, then decltype(T::enumFoo)
is some type, but void_t<decltype(T::enumFoo)>
is void. So we are specializing get_enumFoo<T, void>
to have the field value = T::enumFoo
.
To further reduce the boilerplate behind adding new get_field
traits, you could define a base class:
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
{};