Now, you can use template constraints to fix it, I like to use a little macro to help make the enable_if
boilerplate a little clearer:
#define REQUIRES(...) typename std::enable_if<(__VA_ARGS__), int>::type = 0
Then you could define them directly in the function:
template <typename T, REQUIRES(std::is_pod<T>::value)>
CByteArray serialize(const T& value)
{
return serializePodType(value);
}
template <typename T, REQUIRES(
!std::is_pod<T>::value &&
!std::is_convertible<T, Variant>::value
)>
CByteArray serialize(const T& value)
{
assert(0 == "Unsupported type");
return CByteArray();
}
// This is put last so `serialize` will call the other overloads
template <typename T, REQUIRES(
!std::is_pod<T>::value &&
std::is_convertible<T, Variant>::value
)>
CByteArray serialize(const T& value)
{
return serialize(Variant(value));
}
However, this gets ugly very quickly. First, you have to negate the other conditions to avoid ambiguity. Secondly, the functions have to be ordered so that the other functions are declared or defined before they are called recursively. It doesn't really scale well. If you need to add additional conditions in the future, it can get a lot more complicated.
A better solution is to use conditional overloading with a fix point combinator. The Fit library provides a conditional and fix adaptor, so you don't have to write your own. So in C++14, you could write:
const constexpr serialize = fit::fix(fit::conditional(
FIT_STATIC_LAMBDA(auto, const auto& value,
REQUIRES(std::is_pod<decltype(value)>()))
{
return serializePodType(value);
},
FIT_STATIC_LAMBDA(auto self, const auto& value,
REQUIRES(std::is_convertible<decltype(value), Variant>()))
{
return self(Variant(value));
},
FIT_STATIC_LAMBDA(auto, const auto&)
{
assert(0 == "Unsupported type");
return CByteArray();
}
));
However, if you aren't using C++14 yet, you will have to write them as function objects instead:
struct serialize_pod
{
template<class Self, class T,
REQUIRES(std::is_pod<T>::value)>
CByteArray operator()(Self, const T& value) const
{
return serializePodType(value);
}
};
struct serialize_variant
{
template<class Self, class T,
REQUIRES(std::is_convertible<T, Variant>::value)>
CByteArray operator()(Self self, const T& value) const
{
return self(Variant(value));
}
};
struct serialize_else
{
template<class Self, class T>
CByteArray operator()(Self, const T&) const
{
assert(0 == "Unsupported type");
return CByteArray();
}
};
const constexpr fit::conditional_adaptor<serialize_pod, serialize_variant, serialize_else> serialize = {};
Finally, for your case specifically, you could drop the else part unless you really have a need for a runtime check. Then you can just have the two overloads:
const constexpr serialize = fit::fix(fit::conditional(
FIT_STATIC_LAMBDA(auto, const auto& value,
REQUIRES(std::is_pod<decltype(value)>()))
{
return serializePodType(value);
},
FIT_STATIC_LAMBDA(auto self, const auto& value,
REQUIRES(std::is_convertible<decltype(value), Variant>()))
{
return self(Variant(value));
}
));
So you will have a compiler error instead. The nice thing about using enable_if
and constraints is that the error will be in the user code instead of in your code(with some long backtrace). This helps make it clear that the user is the one making the error instead of a problem with the library code.