简体   繁体   中英

C++ std::enable_if fallback?

I'm setting up a variadic template function to be able to call various function overloads on a specific series of classes. So far, I've been able to "break" the compilation when an unsupported class is passed to the function, but I'd like to be able to provide a valid fallback to handle the "unsupported" scenario at runtime.

The current implementation goes like this:

struct ClassA {};
struct ClassB {};
struct ClassC {};

template<typename T> struct is_my_class         : std::false_type {};
template<>           struct is_my_class<ClassA> : std::true_type  {};
template<>           struct is_my_class<ClassB> : std::true_type  {};

template<typename T>
constexpr bool is_my_class_v = is_my_class<T>::value;

void runOverload(ClassA c) { printf("ClassA overload\n"); }
void runOverload(ClassB c) { printf("ClassB overload\n"); }

template<typename T, typename = std::enable_if_t<is_my_class_v<T>>>
void run(T myClass)
{
    runOverload(myClass);
};

template<typename T, typename... Ts>
void run(T myClass, Ts... classesLeft)
{
    run(myClass);
    run(classesLeft...);
};

int main()
{
    ClassA a;
    ClassB b;
    ClassC c;

    run(a, b); // works
    run(c);    // does not compile
}

Here, the two most promising (but still failing) attempts I've made to have a fallback for run :

1 - Adding a simple ! in front of is_my_class<T> , giving me the following error: error C2995: 'void run(T)': function template has already been defined

template<typename T, typename = std::enable_if_t<!is_my_class_v<T>>>
void run(T myClass)
{
    printf("Not supported\n");
};

2 - Making a more "primary" template definition, which yields a sad but obvious: error C2668: 'run': ambiguous call to overloaded function

template<typename T>
void run(T myClass)
{
    printf("Not supported\n");
};

EDIT I forgot to specify I was looking for a solution also compatible with C++11/14

You could avoid enable_if entirely, and just use a compile-time if to to decide what code to execute:

template<typename T>
void run(T myClass)
{
  if constexpr (is_my_class_v<T>)
    runOverload(myClass);
  else
    printf("Not supported\n"); 
}

Here's a demo .

Even though I'd recommend @cigien 's solution if c++17 is avaiable, I would like to add, that the ambiguity problem can be mitigated pre c++17 by changing the type of the enable_if template argument, and hence changing the signature of the "fallback function". The following code should work fine:

template<typename T, std::enable_if_t<!is_my_class_v<T>, int> = 0> 
//template<typename T>
void run(T myClass) {
  printf("error\n");
};

Full code available on CompilerExplorer , tested on GCC and Clang trunk.

I'd also like to add, that in all use cases I can imagine, it is better to have a compile time error (which you could for instance achieve by using a static assertion instead of SFINAE ).

Here you can read about why the function delcaration is not ambiguous, even when using two "ints" as template arguments.

I wrote following code and it works. I think you just messed up with syntax of SFINAE.

#include <iostream>
#include <type_traits>

struct ClassA {};
struct ClassB {};
struct ClassC {};

template<typename T> struct is_my_class         : std::false_type {};
template<>           struct is_my_class<ClassA> : std::true_type  {};
template<>           struct is_my_class<ClassB> : std::true_type  {};

template<typename T>
constexpr bool is_my_class_v = is_my_class<T>::value;

void runOverload(ClassA c) { printf("ClassA overload\n"); }
void runOverload(ClassB c) { printf("ClassB overload\n"); }

template<typename T, std::enable_if_t<is_my_class_v<T>,int> = 0>
void run(T myClass)
{
    runOverload(myClass);
};

template<typename T, std::enable_if_t<not is_my_class_v<T>,int> = 0>
void run(T myClass)
{
    printf("Not supported\n");
};

// wrote an extra SFINEA here to ensure that Ts aren't empty - else it might be an ODR issue despite the fact that it compiles
template<typename T, typename... Ts, std::enable_if_t<sizeof...(Ts) != 0,int> = 0>
void run(T myClass, Ts... classesLeft)
{
    run(myClass);
    run(classesLeft...);
};

int main()
{
    ClassA a;
    ClassB b;
    ClassC c;

    run(a, b);
    run(c);
}

It prints

ClassA overload
ClassB overload
Not supported

Have a fallback runOverload template

template<typename T>
void runOverload(T myClass)
{
    printf("Not supported\n");
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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