C++: Manual disambiguation of partial specializati

2019-06-06 01:58发布

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.

2条回答
淡お忘
2楼-- · 2019-06-06 02:30

I always was manually resolving ambiguities with std::enable_if, but this approach can become boilerplate when you have many alternatives:

#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";
}

http://ideone.com/jb70Cd

查看更多
成全新的幸福
3楼-- · 2019-06-06 02:35

I personally would solve this with overloading rather than class template partial specializations. This way, you can easily introduce a tie-breaker:

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";
}

Live example. Note that void belongs to the "Undefined" category, it's not a POD.

查看更多
登录 后发表回答