I am implementing a generic class, which should behave differently for different sets of types (not only for different discrete types). The goal is to serialize objects of different types to send them over custom protocol (but it is more educational task rather than something pragmatical; I am a student that is interested in distributed computing). For example, I need to send floats and integers differently. I also want to have a default handler of other POD types. But I need to override the behavior for some my POD types...
I have found that SFINAE method very useful and implement generic classes for integral and floating point types, for my custom types using SFINAE principle and partial specialization (see code below). But when I have tried to implement a handler of other POD types hoping that other handlers will overlap more general POD-types handler, I faced with an ambiguity issue. Actually there is no overlapping of possible specializations of my GenericObject class for POD types and a subsets of it - integral and floating point types.
I was trying to implement a manual ordering of specializations, read much about partial ordering, about that one specialization is preferred than another if it is more specialized. But I have failed to solve the problem. I have no ideas how to disambiguate my partial specializations in a manual manner.
The solution with excluding a set of floating point types and integral types of my POD-types handler is not acceptable for me, because this way produces an excess dependencies between handlers. I hope there is a right way to resolve my problem. For example, at the start of the program all static resources are initialized with several priorities. In GCC I can control the sequence of such initialization with an attribute constructor: __ attribute__((constructor(101))) or a similar attribute init_priority. I would be pleased if I can reorder template partial specialization in a such way.
Could you suggest me anything?
Here is my code:
#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
}
Here is the compile time errors:
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
I use GCC 4.8:
gcc version 4.8.0 20120314 (experimental) [trunk revision 185382] (Ubuntu/Linaro 20120314-0ubuntu2)
Thank you in advance.
I always was manually resolving ambiguities with std::enable_if, but this approach can become boilerplate when you have many alternatives:
http://ideone.com/jb70Cd
I personally would solve this with overloading rather than class template partial specializations. This way, you can easily introduce a tie-breaker:
Live example. Note that
void
belongs to the"Undefined"
category, it's not a POD.