Резервный вариант С++ std::enable_if?

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

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

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");
};

EDIT Я забыл указать, что искал решение, также совместимое с C++ 11/14.


person VincentDM    schedule 30.04.2020    source источник


Ответы (4)


Несмотря на то, что я бы рекомендовал решение @cigien, если С++ 17 доступен, я хотел бы добавить, что проблему неоднозначности можно смягчить до С++ 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, протестирован на GCC и Clang.

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

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

person mutableVoid    schedule 30.04.2020
comment
Какова цель = 0 в конце параметров шаблона? - person VincentDM; 30.04.2020
comment
По умолчанию для шаблона целочисленного типа устанавливается значение (которое в данном случае равно нулю). Вы делаете более или менее то же самое с вашим типом enable_if, там вы по умолчанию используете имя типа для типа, содержащегося в enable_if - person mutableVoid; 30.04.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"); 
}

Вот демонстрация.

person cigien    schedule 30.04.2020
comment
Думаю, мне понадобится что-то вроде std::conjunction для обработки вариативной версии? - person VincentDM; 30.04.2020
comment
Нет, вариационная версия остается прежней. Какая ветвь constexpr принимается, решается во время компиляции, так же, как и вариативное расширение. Добавил демо. - person cigien; 30.04.2020
comment
if constexpr () правильно ли C++17? Любое предложение для решения с обратной совместимостью? - person VincentDM; 30.04.2020
comment
Да, продолжайте и добавьте тег для интересующей вас языковой версии. - person cigien; 30.04.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);
}

Он печатает

Перегрузка класса A
Перегрузка класса B
Не поддерживается

person ALX23z    schedule 30.04.2020
comment
Это работает, но проблема с двусмысленностью связана с тем, что аргумент шаблона из enable_if является типом в коде OP, который исправлен в вашем и моем ответе, изменив его на int. Неоднозначность возникает только из-за вариативной перегрузки и обычно не является проблемой при использовании SFINAE. - person mutableVoid; 30.04.2020
comment
@mutableVoid Я считаю, что цель SFINEA также заключается в разрешении перегрузок. Если один метод не позволяет это сделать, то лучше использовать тот, который делает. Нет? Если кто-то явно не хочет отключить перегрузки (хотя на самом деле это не работает должным образом). - person ALX23z; 30.04.2020
comment
Конечно, я думаю, что мой комментарий мог быть несколько неясным, я просто хотел указать, что синтаксис SFINAE, который использовал OP, обычно прекрасен, и что в этом случае невозможно использовать тип в качестве используемого аргумента шаблона в enable_if, потому что это неоднозначно. Тем не менее, я должен указать, что я был неправ в своей оценке причины неоднозначности: получается, что исходный код все еще считается неоднозначным, если вариационная функция закомментирована. - person mutableVoid; 30.04.2020
comment
Кстати, я нашел причину двусмысленности, причина того, что одно решение неоднозначно, а другое не указано здесь: -of-type-and-non-type-template-parameters" title="sfinae работает по-разному в случаях типовых и нетиповых параметров шаблона"> stackoverflow.com/questions/36499008/ - person mutableVoid; 30.04.2020

Иметь запасной runOverload шаблон

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