Подтвердить что ты не робот

Какой правильный способ исправить эту неоднозначность разрешения шаблона?

Предположим, что я написал:

template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
void foo() { std::cout << "T is integral." << std::endl; }

template <typename T>
void foo() { std::cout << "Any T." << std::endl; }

int main() { foo<short>(); }

Когда я скомпилирую это, я получаю ошибку об неоднозначности вызова (и нет ошибки, если, скажем, я заменю short на float). Как мне исправить этот код, чтобы получить верхнюю версию для интегральных типов и более низкую версию в противном случае?

Бонусные баллы, если ваше предложение масштабируется в случае нескольких специализированных версий foo() в дополнение к общему.

4b9b3361

Ответ 1

Мне нравится подход Xeo для решения этой проблемы. Позвольте сделать некоторую отправку тега с резервным. Создайте структуру chooser, которая наследует от себя весь путь вниз:

template <int I>
struct choice : choice<I + 1> { };

template <> struct choice<10> { }; // just stop somewhere

So choice<x> можно конвертировать в choice<y> для x < y, что означает, что choice<0> - лучший выбор. Теперь вам нужен последний случай:

struct otherwise{ otherwise(...) { } };

С помощью этого механизма мы можем перенаправить наш основной шаблон функции с дополнительным аргументом:

template <class T> void foo() { foo_impl<T>(choice<0>{}); }

И затем сделайте свой главный выбор и ваш худший вариант... ничего:

template <class T, class = std::enable_if_t<std::is_integral<T>::value>>
void foo_impl(choice<0> ) {
    std::cout << "T is integral." << std::endl;
}

template <typename T>
void foo_impl(otherwise ) {
    std::cout << "Any T." << std::endl;
}

Это упрощает добавление дополнительных параметров в середине. Просто добавьте перегрузку для choice<1> или choice<2> или что-то еще. Нет необходимости в непересекающихся условиях. Этому рекомендуется преференциальное разрешение перегрузки для choice<x>.

Еще лучше, если вы дополнительно передадите T в качестве аргумента, потому что перегрузка лучше, чем специализация:

template <class T> struct tag {};
template <class T> void foo() { foo_impl(tag<T>{}, choice<0>{}); }

И тогда вы можете пойти на дикую природу:

// special 1st choice for just int
void foo_impl(tag<int>, choice<0> );

// backup 1st choice for any integral
template <class T, class = std::enable_if_t<std::is_integral<T>::value>>
void foo_impl(tag<T>, choice<0> );

// 2nd option for floats
template <class T, class = std::enable_if_t<std::is_floating_point<T>::value>>
void foo_impl(tag<T>, choice<1> );

// 3rd option for some other type trait
template <class T, class = std::enable_if_t<whatever<T>::value>>
void foo_impl(tag<T>, choice<2> );

// fallback 
template <class T>
void foo_impl(tag<T>, otherwise );

Ответ 2

Еще одна опция, использующая отправку тегов (С++ 11):

#include <iostream>

void foo_impl(std::false_type) {
    std::cout << "Any T." << std::endl;
}

void foo_impl(std::true_type) {
    std::cout << "T is integral." << std::endl;
}

template <typename T>
void foo() {
    foo_impl(std::is_integral<typename std::remove_reference<T>::type>());
  //foo_impl(std::is_integral<typename std::remove_reference_t<T>>()); // C++14
}

int main() { 
    foo<short>();  // --> T is integral.
    foo<short&>(); // --> T is integral.
    foo<float>();  // --> Any T.
}

Заимствован из Скотта Майера Эффективный современный элемент С++ 27.

Ответ 3

Один из способов:

template <typename T, typename std::enable_if_t<std::is_integral<T>::value>* = nullptr>
void foo() { std::cout << "T is integral." << std::endl; }

template <typename T, typename std::enable_if_t<not std::is_integral<T>::value>* = nullptr>
void foo() { std::cout << "Any T." << std::endl; }

Другой способ - отложить до объекта функции шаблона:

template<class T, typename = void>
struct foo_impl
{
    void operator()() const {
        std::cout << "Any T." << std::endl;
    }
};

template<class T>
struct foo_impl<T, std::enable_if_t<std::is_integral<T>::value>>
{
    void operator()() const {
        std::cout << "T is integral." << std::endl;
    }
};

template<class T>
void foo() {
    return foo_impl<T>()();
}

Ответ 4

Один из способов сделать это:

template <typename T>
std::enable_if_t<std::is_integral<T>::value, void> foo () {
    std::cout << "integral version" << std::endl;
}

template <typename T>
std::enable_if_t<!std::is_integral<T>::value, void> foo () {
    std::cout << "general version" << std::endl;
}

с использованием:

foo<int> ();
foo<double> ();
struct X {};
foo<X> ();

:

integral version
general version
general version

Ответ 5

AFAIK, sfinae применим к параметрам функции, поэтому попробуйте добавить параметр зависимого типа со значением по умолчанию

template <typename T>
void foo(typename std::enable_if_t<std::is_integral<T>::value>* = 0) 
{ std::cout << "T is integral." << std::endl; }

template <typename T>
void foo(typename std::enable_if_t<!std::is_integral<T>::value>* = 0) 
{ std::cout << "Any T." << std::endl; }