PowerGamer
PowerGamer

Reputation: 2136

Making scoped enumerations comparable to underlying type

I am trying to make scoped enumerations in my program comparable to underlying type but the following code does not work. Is it because of poor C++11 standard support in the compiler I am using (VC11) or is it because the code breaks some rules from C++11 standard? In latter case which rules exactly are being broken (references to specific standard clauses are welcome)?

#include <type_traits>
enum class Test: short int { A,B,C };
template<typename E> bool operator != (E e, typename std::underlying_type<E>::type n)
{
    return static_cast<typename std::underlying_type<E>::type>(e) != n;
}
template<typename E> bool operator != (typename std::underlying_type<E>::type n, E e)
{
    return static_cast<typename std::underlying_type<E>::type>(e) != n;
}
int main()
{
    short int x = 123;
    x != Test::B; // compilation error
}

Here is why I think my code should be C++11 compliant. A quote from C++11 standard (14.8.3.1):

For each function template, if the argument deduction and checking succeeds, the template arguments (deduced and/or explicit) are used to synthesize the declaration of a single function template specialization which is added to the candidate functions set to be used in overload resolution. If, >for a given function template, argument deduction fails, no such function is added to the set of >candidate functions for that template.

EDIT. My code is not C++11 compliant (thanks Vaughn Cato and Andy Prowl for explanation). The alternative working code is provided in Andy Prowl's answer.

P.S. After all I ended up making unscoped enums scoped using namespaces:

namespace Test_ {
    enum Test { A,B,C };
};
using Test_::Test;

namespace Test2_ {
    enum Test2 { Z,Y,B };
};
using Test2_::Test2;

Upvotes: 4

Views: 492

Answers (2)

Vaughn Cato
Vaughn Cato

Reputation: 64308

Section 14.8.2 paragraph 8 of the C++11 standard states:

If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed if written using the substituted arguments. [ Note: Access checking is done as part of the substitution process. — end note ] Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure. [ Note: The evaluation of the substituted types and expressions can result in side effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such side effects are not in the “immediate context” and can result in the program being ill-formed. — end note ]

In your case, instantiating underlying_type causes a failure, but not in the immediate context, so it is not a type deduction failure, so SFINAE does not apply.

Upvotes: 1

Andy Prowl
Andy Prowl

Reputation: 126432

You could use SFINAE to rule out the instantiation of the signature of your comparison operators (and therefore the instantiation of std::underlying_type<T>) when the corresponding argument is not an enumeration:

#include <type_traits>

template<typename E, 
    typename std::enable_if<std::is_enum<E>::value>::type* = nullptr>
bool operator != (E e, typename std::underlying_type<E>::type n)
{
    return static_cast<typename std::underlying_type<E>::type>(e) != n;
}

template<typename E, 
    typename std::enable_if<std::is_enum<E>::value>::type* = nullptr>
bool operator != (typename std::underlying_type<E>::type n, E e)
{
    return static_cast<typename std::underlying_type<E>::type>(e) != n;
}

Here is a live example.

EDIT:

Since VC11 seems to lack support for default arguments on template parameters of a function template, here is an alternative solution:

template<typename E>
typename std::enable_if<std::is_enum<E>::value, bool>::type
operator != (E e, typename std::underlying_type<E>::type n)
{
    return static_cast<typename std::underlying_type<E>::type>(e) != n;
}

template<typename E>
typename std::enable_if<std::is_enum<E>::value, bool>::type
operator != (typename std::underlying_type<E>::type n, E e)
{
    return static_cast<typename std::underlying_type<E>::type>(e) != n;
}

And a live example of course.

Upvotes: 4

Related Questions