Bulletmagnet
Bulletmagnet

Reputation: 6010

How to std::enable_if only if a certain template has a specialization for a given class

I have the following template:

namespace std {
template<typename Enum>
typename std::enable_if<std::is_enum<Enum>::value, std::ostream&>::type
operator<<(std::ostream& strm, Enum e)
{
    return strm << helper_of<Enum>::to_string(e);
}
}

which helps google-test display human-readable diagnostics when comparing hobbits:

template <typename T> struct enumclass {}; // generic template

template <typename T>
using helper_of = typename enumclass<T>::helper; // more mnemonic

namespace MiddleEarth {
enum class Hobbit { Bilbo, Frodo, Sam };

struct HobbitHelper{
    std::string to_string(Hobbit H);
    Hobbit from_string(std::string const& s); // Hobbit-forming
};

template <> struct enumclass<Hobbit> {
    using helper = HobbitHelper; // links Hobbit to its helper
}
}

The enable_if is there to prevent this templated operator<< from being applied to any old class (the naive version without enable_if is ambiguous for classes which already have streaming operators, e.g. std::string).

However, if there is an enum which doesn't specialize enumclass,

enum class Wizard { Gandalf, Radagast, Saruman };
const Wizard g = Wizard::Gandalf, s = Wizard::Saruman;

then the following fails to compile

EXPECT_EQ(g, s);

with error: no type named 'helper' in 'aws::enumclass<Wizard>' because the compiler tries to apply the templated operator<< to Wizard.

Is it possible to construct an enable_if that would only apply this operator<< if there is a specialization of enumclass<Enum> ? Google-test then would fall back to display the raw bytes of Wizard and it would compile.

Failing that, is it possible to construct an enable_if which would only allow types in a certain namespace (e.g. MiddleEarth)? This would solve the problem if Wizard is not in the MiddleEarth namespace. All the enums in MiddleEarth are supposed to have a specialization for enumclass.

Upvotes: 2

Views: 931

Answers (1)

Barry
Barry

Reputation: 302698

You can just move the helper_of substitution into the template specification itself:

template <typename Enum,
          typename Helper = helper_of<Enum>>
std::ostream& operator<<(std::ostream& strm, Enum e)
{
    return strm << Helper::to_string(e);
}

That way, if the helper_of substitution fails (that is, enumclass isn't specialized for the given Enum), the entire overload will be thrown out due to SFINAE rather than being a hard compile error - since now we're in the immediate context of the substitution itself.

Upvotes: 5

Related Questions