Специализация частичных шаблонов с помощью 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
Это именно то, чего я хочу избежать, так как в моих реальных случаях существует множество специализированных реализаций, и я бы хотел избежать очень длительного (ошибки, подверженного!) условия для реализации по умолчанию.
Ответы
Ответ 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"