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

Специализация частичных шаблонов с помощью enable_if: выполнить стандартную реализацию

Использование С++ 11 enable_if Я хочу определить несколько специализированных реализаций для функции (в зависимости от типа параметра, скажем), а также реализации по умолчанию. Каков правильный способ его определения?

Следующий пример не работает должным образом, так как вызывается "общая" реализация, независимо от типа T.

#include <iostream>

template<typename T, typename Enable = void>
void dummy(T t)
{
  std::cout << "Generic: " << t << std::endl;
}


template<typename T, typename std::enable_if<std::is_integral<T>::value>::type>
void dummy(T t)
{
  std::cout << "Integral: " << t << std::endl;
}


template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type>
void dummy(T t)
{
  std::cout << "Floating point: " << t << std::endl;
}

int main() {
  dummy(5); // Print "Generic: 5"
  dummy(5.); // Print "Generic: 5"
}

Одно из решений моего минимального примера состоит в явном объявлении "общей" реализации как не для интегральных, так и для типов с плавающей запятой, используя

std::enable_if<!std::is_integral<T>::value && !std::is_floating_point<T>::value>::type

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

4b9b3361

Ответ 1

Функция не может быть частично специализирована. Я предполагаю, что вы хотите, чтобы предпочесть те перегрузки, которые содержат явное условие? Один из способов достижения этого - использование многоадгезивных аргументов при объявлении функции default, поскольку функция многоточия имеет более низкий приоритет в порядке разрешения перегрузки:

#include <iostream>

template<typename T>
void dummy_impl(T t, ...)
{
  std::cout << "Generic: " << t << std::endl;
}


template<typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void dummy_impl(T t, int)
{
  std::cout << "Integral: " << t << std::endl;
}


template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
void dummy_impl(T t, int)
{
  std::cout << "Floating point: " << t << std::endl;
}

template <class T>
void dummy(T t) {
   dummy_impl(t, int{});
}

int main() {
  dummy(5); 
  dummy(5.); 
  dummy("abc"); 
}

Вывод:

Integral: 5
Floating point: 5
Generic: abc

[live demo]

Другой вариант, упоминаемый в комментарии @doublep, заключается в использовании структуры с реализацией вашей функции, а затем частично ее специализируется.

Ответ 2

Вы можете ввести rank, чтобы перенести некоторые из ваших перегрузок:

template <unsigned int N>
struct rank : rank<N - 1> { };

template <>
struct rank<0> { };

Затем вы можете определить свои перегрузки dummy следующим образом:

template<typename T>
void dummy(T t, rank<0>)
{
    std::cout << "Generic: " << t << std::endl;
}

template<typename T, 
         typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void dummy(T t, rank<1>)
{
    std::cout << "Integral: " << t << std::endl;
}

template<typename T, 
         typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
void dummy(T t, rank<1>)
{
    std::cout << "Floating point: " << t << std::endl;
}

Затем вы можете скрыть вызов за dispatch:

template <typename T>
void dispatch(T t)
{
   return dummy(t, rank<1>{});
}

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

int main() 
{
    dispatch(5);    // Print "Integral: 5"
    dispatch(5.);   // Print "Floating point: 5"
    dispatch("hi"); // Print "Generic: hi"
}

живой пример в wandbox


Объяснение:

Использование rank вводит "приоритет", потому что неявные преобразования необходимы для преобразования rank<X> в rank<Y>, когда X > Y. dispatch сначала пытается вызвать dummy с помощью rank<1>, отдавая приоритет вашим ограниченным перегрузкам. Если enable_if терпит неудачу, rank<1> неявно преобразуется в rank<0> и входит в "резервный" случай.


Бонус: здесь реализация С++ 17 с использованием if constexpr(...).

template<typename T>
void dummy(T t)
{
    if constexpr(std::is_integral_v<T>)
    {
        std::cout << "Integral: " << t << std::endl;
    }
    else if constexpr(std::is_floating_point_v<T>)
    {
        std::cout << "Floating point: " << t << std::endl;
    }
    else
    {
        std::cout << "Generic: " << t << std::endl;
    }
}

живой пример в wandbox

Ответ 3

Я бы использовал диспетчеризацию меток так:

namespace Details
{
    namespace SupportedTypes
    {
        struct Integral {};
        struct FloatingPoint {};
        struct Generic {};
    };


    template <typename T, typename = void>
    struct GetSupportedType
    {
        typedef SupportedTypes::Generic Type;
    };

    template <typename T>
    struct GetSupportedType< T, typename std::enable_if< std::is_integral< T >::value >::type >
    {
        typedef SupportedTypes::Integral Type;
    };

    template <typename T>
    struct GetSupportedType< T, typename std::enable_if< std::is_floating_point< T >::value >::type >
    {
        typedef SupportedTypes::FloatingPoint Type;
    };

    template <typename T>
    void dummy(T t, SupportedTypes::Generic)
    {
        std::cout << "Generic: " << t << std::endl;
    }

    template <typename T>
    void dummy(T t, SupportedTypes::Integral)
    {
        std::cout << "Integral: " << t << std::endl;
    }

    template <typename T>
    void dummy(T t, SupportedTypes::FloatingPoint)
    {
        std::cout << "Floating point: " << t << std::endl;
    }
} // namespace Details

Затем скройте код плиты котла так:

template <typename T>
void dummy(T t)
{
    typedef typename Details::GetSupportedType< T >::Type SupportedType;
    Details::dummy(t, SupportedType());
}

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

Затем вы просто вызываете правильную перегрузку dummy, предоставляя экземпляр нужного тега.

Наконец, вызовите dummy:

dummy(5); // Print "Generic: 5"
dummy(5.); // Print "Floating point: 5"
dummy("lol"); // Print "Generic: lol"