C++ std::enable_if 回退?

C++ std::enable_if fallback?

我正在设置可变参数模板函数,以便能够在特定的 classes 系列上调用各种函数重载。到目前为止,当将不受支持的 class 传递给函数时,我已经能够 "break" 进行编译,但我希望能够提供有效的回退来处理 "unsupported" 运行时场景。

当前的实现是这样的:

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
}

在这里,我为run做的两个最有希望(但仍然失败)的尝试:

1 - 在 is_my_class<T> 前面添加一个简单的 !,出现以下错误: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 - 制作更多 "primary" 模板定义,这会产生一个悲伤但明显的结果:error C2668: 'run': ambiguous call to overloaded function

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

编辑 我忘了说明我正在寻找一个也兼容 C++11/14

的解决方案

您可以完全避免 enable_if,只需使用编译时 if 来决定要执行的代码:

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

这是一个demo

有后备 runOverload 模板

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

尽管如果 c++17 可用,我会推荐@cigien 的解决方案,但我想补充一点,通过更改 enable_if 模板参数,因此改变了 "fallback function" 的签名。以下代码应该可以正常工作:

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

完整代码在 CompilerExplorer 上可用,已在 GCC 和 Clang trunk 上测试。

我还想补充一点,在我能想象到的所有用例中,最好有一个编译时错误(例如,您可以使用 static assertion 而不是 SFINAE).

Here 您可以了解为什么即使使用两个 "ints" 作为模板参数,函数声明也不会模棱两可。

我写了下面的代码并且它有效。我认为你只是搞砸了 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);
}

它打印

ClassA overload
ClassB overload
Not supported