C ++ std :: enable_if запасной вариант? - PullRequest
1 голос
/ 30 апреля 2020

Я настраиваю шаблонную функцию variadi c, чтобы можно было вызывать различные перегрузки функций для определенной серии c классов. До сих пор я был в состоянии «прервать» компиляцию, когда в функцию передан неподдерживаемый класс, но я хотел бы иметь возможность предоставить допустимый запасной вариант для обработки «неподдерживаемого» сценария во время выполнения.

Текущая реализация выглядит следующим образом:

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 - Создание более "первичного" определения шаблона, которое приводит к печальному, но очевидному : error C2668: 'run': ambiguous call to overloaded function

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

РЕДАКТИРОВАТЬ Я забыл указать, что ищу решение, также совместимое с C ++ 11/14

Ответы [ 4 ]

3 голосов
/ 30 апреля 2020

Вы можете полностью избежать enable_if и просто использовать время компиляции if, чтобы решить, какой код выполнять:

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

Вот демо .

2 голосов
/ 30 апреля 2020

Несмотря на то, что я бы порекомендовал решение @cigien, если c ++ 17 доступен, я хотел бы добавить, что проблему неоднозначности можно решить до c ++ 17, изменив тип шаблона enable_if аргумент, и, следовательно, изменение сигнатуры "резервной функции". Следующий код должен работать нормально:

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 , протестирован на G CC и Clang trunk.

Я также хотел бы добавим, что во всех случаях использования, которые я могу себе представить, лучше иметь ошибку времени компиляции (которую можно, например, достичь, используя static assertion вместо SFINAE).

Здесь вы можете прочитать о том, почему функция делкарации не является неоднозначной, даже если в качестве аргументов шаблона используются две "целые".

1 голос
/ 30 апреля 2020

Я написал следующий код, и он работает. Я думаю, что вы только что испортили синтаксис 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
Перегрузка ClassB
Не поддерживается

0 голосов
/ 30 апреля 2020

Есть запасной вариант runOverload шаблон

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