Я реализую универсальный класс, который должен вести себя по-разному для разных наборов типов (не только для разных дискретных типов). Цель состоит в том, чтобы сериализовать объекты различных типов для отправки их по специальному протоколу (но это скорее образовательная задача, чем прагматическая; я студент, интересующийся распределенными вычислениями). Например, мне нужно отправлять числа и числа с плавающей точкой по-разному. Я также хочу иметь обработчик по умолчанию для других типов POD. Но мне нужно переопределить поведение для некоторых моих типов POD …
Я обнаружил, что метод SFINAE очень полезен и реализует универсальные классы для целочисленных типов и типов с плавающей запятой, для моих пользовательских типов, использующих принцип SFINAE и частичную специализацию (см. Код ниже). Но когда я попытался реализовать обработчик других типов POD, надеясь, что другие обработчики будут перекрывать более общий обработчик типов POD, я столкнулся с проблемой неоднозначности. На самом деле нет никакого перекрытия возможных специализаций моего класса GenericObject для типов POD и его подмножеств — целочисленных типов и типов с плавающей запятой.
Я пытался реализовать ручное упорядочение специализаций, много читал о частичном упорядочении, о том, что одна специализация предпочтительнее другой, если она более специализированная. Но мне не удалось решить проблему. Я понятия не имею, как устранить неоднозначность моей частичной специализации вручную.
Решение с исключением набора типов с плавающей точкой и целочисленных типов моего обработчика POD-типов для меня неприемлемо, потому что этот способ создает избыточные зависимости между обработчиками. Я надеюсь, что есть правильный способ решить мою проблему. Например, при запуске программы все статические ресурсы инициализируются с несколькими приоритетами. В GCC я могу контролировать последовательность такой инициализации с помощью конструктора атрибута: __ attribute __ ((constructor (101))) или аналогичного атрибута init_priority. Я был бы рад, если бы я мог изменить порядок частичной специализации шаблона таким образом.
Не могли бы вы предложить мне что-нибудь?
Вот мой код:
#include <type_traits>
#include <iostream>
#include <cxxabi.h>
// General form
template <typename T, typename Enable0 = void>
struct GenericObject {
char * description() {
return (char *)"Undefined";
}
};
// Specialization for integral types
template <typename T>
struct GenericObject<T, typename std::enable_if<std::is_integral<T>::value>::type> {
char * description() {
return (char *)"Integral";
}
};
// Specialization for real types
template <typename T>
struct GenericObject<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
char * description() {
return (char *)"Real";
}
};
// Specialization for other POD types. It MUST be less specialized than specializations for real and integral types, because in other way there will be an ambiguity, because every integral type is also a POD.
/*
HERE IS MY PROBLEM
*/
template <typename T>
struct GenericObject<T, typename std::enable_if<std::is_pod<T>::value>::type> {
char * description() {
return (char *)"POD";
}
};
// Declaration of types
struct IAmDefined {};
struct IAmUndefinedPOD {};
struct IAmUndefinedComplexClass : virtual IAmUndefinedPOD {};
// Specialization for IAmDefined class and also the most specialized template specialization.
template <>
struct GenericObject<IAmDefined> {
char * description() {
return (char *)"Defined";
}
};
// Produces nice output
std::string demangle(const char *raw) {
int status;
char *demangled = abi::__cxa_demangle(raw, 0, 0, &status);
std::string result(demangled);
free(demangled);
return result;
}
template <typename T>
void testObject() {
GenericObject<T> object;
std::cout << demangle(typeid(T).name()) << ": " << object.description() << std::endl;
}
int main() {
testObject<int>(); // Integral
testObject<long>(); // Integral
testObject<float>(); // Real
testObject<double>(); // Real
testObject<void>(); // POD
testObject<IAmDefined>(); // Defined
testObject<IAmUndefinedPOD>(); // POD
testObject<IAmUndefinedComplexClass>(); // Undefined
}
Вот ошибки времени компиляции:
g++ --std=c++11 main.cc -o specialization-of-sets
main.cc: In instantiation of 'void testObject() [with T = int]':
main.cc:85:21: required from here
main.cc:68:22: error: ambiguous class template instantiation for 'struct GenericObject<int, void>'
main.cc:15:8: error: candidates are: struct GenericObject<T, typename std::enable_if<std::is_integral<_Tp>::value>::type>
main.cc:36:8: error: struct GenericObject<T, typename std::enable_if<std::is_pod<_Tp>::value>::type>
main.cc:68:22: error: 'GenericObject<int, void> object' has incomplete type
main.cc: In instantiation of 'void testObject() [with T = long int]':
main.cc:86:22: required from here
main.cc:68:22: error: ambiguous class template instantiation for 'struct GenericObject<long int, void>'
main.cc:15:8: error: candidates are: struct GenericObject<T, typename std::enable_if<std::is_integral<_Tp>::value>::type>
main.cc:36:8: error: struct GenericObject<T, typename std::enable_if<std::is_pod<_Tp>::value>::type>
main.cc:68:22: error: 'GenericObject<long int, void> object' has incomplete type
main.cc: In instantiation of 'void testObject() [with T = float]':
main.cc:87:23: required from here
main.cc:68:22: error: ambiguous class template instantiation for 'struct GenericObject<float, void>'
main.cc:23:8: error: candidates are: struct GenericObject<T, typename std::enable_if<std::is_floating_point<_Tp>::value>::type>
main.cc:36:8: error: struct GenericObject<T, typename std::enable_if<std::is_pod<_Tp>::value>::type>
main.cc:68:22: error: 'GenericObject<float, void> object' has incomplete type
main.cc: In instantiation of 'void testObject() [with T = double]':
main.cc:88:24: required from here
main.cc:68:22: error: ambiguous class template instantiation for 'struct GenericObject<double, void>'
main.cc:23:8: error: candidates are: struct GenericObject<T, typename std::enable_if<std::is_floating_point<_Tp>::value>::type>
main.cc:36:8: error: struct GenericObject<T, typename std::enable_if<std::is_pod<_Tp>::value>::type>
main.cc:68:22: error: 'GenericObject<double, void> object' has incomplete type
Я использую GCC 4.8:
gcc версия 4.8.0 20120314 (экспериментальная) [ствол ревизия 185382] (Ubuntu / Linaro 20120314-0ubuntu2)
Заранее спасибо.
Я лично решил бы это с перегрузкой, а не частичной специализацией шаблона класса. Таким образом, вы можете легко ввести тай-брейк:
template<class T> struct type{};
namespace detail{
template<class T> using Invoke = typename T::type;
template<class C, class T = void> using EnableIf = Invoke<std::enable_if<C::value, T>>;
template<class T> using EType = type<EnableIf<T>>;
// we need two tie-breakers here, one for general vs specialized case
// and one for specialized vs more specialized case
template<class T>
char const* describe(EType<std::is_integral<T>>, int, int){ return "Integral"; }
template<class T>
char const* describe(EType<std::is_floating_point<T>>, int, int){ return "Real"; }
template<class T>
char const* describe(EType<std::is_pod<T>>, int, long){ return "POD"; }
template<class T>
char const* describe(type<void>, long, long){ return "Undefined"; }
} // detail::
template<class T>
char const* describe(type<T>){
// literal '0' is 'int', as such 'int' overloads are preferred
return detail::describe<T>(type<void>(), 0, 0);
}
// ...
// simple overload for specialized types
char const* describe(type<IAmDefined>){
return "Defined";
}
Живой пример. Обратите внимание, что void
принадлежит к "Undefined"
категория, это не POD.
Я всегда вручную разрешал неоднозначности с помощью std :: enable_if, но этот подход может стать стандартным, когда у вас есть много альтернатив:
#include <iostream>
#include <type_traits>
using namespace std;
template<typename T>
const char* describe(T,
typename enable_if<is_integral<T>::value>::type* = 0)
{
return "integral";
}
template<typename T>
const char* describe(T,
typename std::enable_if<is_floating_point<T>::value>::type* = 0)
{
return "floating_point";
}
template<typename T>
const char* describe(T,
typename std::enable_if<!std::is_integral<T>::value && !std::is_floating_point<T>::value && std::is_pod<T>::value>::type* = 0)
{
return "pod";
}