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

17

Использование C ++ 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

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

    
задан Bruno 16.06.2017 в 11:19
источник

3 ответа

12

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

    
ответ дан W.F. 16.06.2017 в 11:39
источник
  • Другой вариант заключается в том, чтобы поместить реализацию в фиктивный класс / структуру и частично специализироваться на этом. Хотя это, вероятно, связано с большим количеством ввода текста. –  doublep 16.06.2017 в 11:41
  • Я заметил, что на самом деле нужно, чтобы второй параметр шаблона был указателем с нулевым значением по умолчанию для работы конструкции. Можете ли вы объяснить, почему это так, или указать мне какое-то объяснение? Благодаря! –  Bruno 16.06.2017 в 14:15
  • @doublep больше набрания вполне приемлемо, если это означает, что каждый может читать и поддерживать ваш код. Это определенно стоит усилий. –  mister why 16.06.2017 в 15:25
  • @Bruno Я использую его для того, чтобы заставить компилятор создать экземпляр типа параметра непигового шаблона. Без такого экземпляра sfinae не будет срабатывать. –  W.F. 16.06.2017 в 16:08
  • @misterwhy, используя альтернативные аргументы, а также предлагаемые диспетчером приоритетов VittorioRomeo, все скрытые позади, например. пространство имен реализации - хорошо известные и установленные механизмы для работы с sfinae. Более того, вариационные аргументы elision были одним из немногих помощников при выполнении sfinae, прежде чем c ++ 11 - это вещь. –  W.F. 16.06.2017 в 16:25
12

Вы можете ввести 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> и входит в «резервный» случай.

Бонус: : реализация C ++ 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

    
ответ дан Vittorio Romeo 16.06.2017 в 11:43
источник
  • Хорошее решение, спасибо! –  Bruno 16.06.2017 в 11:49
  • @Bruno Я не согласен, я думаю, что это решение не выражает намерения отправить правильный метод. Прочтите этот код пару месяцев спустя; хотя это довольно просто, вы можете законно задаться вопросом о значении ранга. –  mister why 16.06.2017 в 15:24
5

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

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"
    
ответ дан mister why 16.06.2017 в 12:03
источник