To me it looks like MSVC is correct here.
First, because template argument deduction should happen before overload resolution to form a set of candidate functions:
Before overload resolution begins, the functions selected by name
lookup and template argument deduction are combined to form the set of
candidate functions
And then, since the non-member candidates (in this case template specialisation of the operator+
overload with the enum_container::<anonymous_enum>
parameters) precede built-in candidates (conversion to the underlying built-in type, for which a user-defined overload would not be possible), the compiler is meant to resolve the overload to the instance of the template with the user-defined enum-type:
- non-member candidates: For the operators where operator overloading
permits non-member forms, all declarations found by unqualified name
lookup of operator@ in the context of the expression (which may
involve ADL), except that member function declarations are ignored and
do not prevent the lookup from continuing into the next enclosing
scope. If both operands of a binary operator or the only operand of a
unary operator has enumeration type, the only functions from the
lookup set that become non-member candidates are the ones whose
parameter has that enumeration type (or reference to that enumeration
type)
- built-in candidates: For
operator,
, the unary operator&
, and the operator->
, the set of built-in candidates is empty. For other
operators built-in candidates are the ones listed in built-in operator
pages as long as all operands can be implicitly converted to their
parameters. If any built-in candidate has the same parameter list as a
non-member candidate that isn't a function template specialization, it
is not added to the list of built-in candidates. When the built-in
assignment operators are considered, the conversions from their
left-hand arguments are restricted: user-defined conversions are not
considered.
Thus, the fact that GCC/Clang ignore the existence of the user-defined overload of operator+
under the enum
definition doesn't seem to be compliant with the rules listed above. The same expression inside of a block scope causes exactly same error for all compilers (and I couldn't find any rationale why this might be different for the scope of enum
definition):
struct enum_container {
enum {
item1 = 1,
item2 = 2,
item3 = item1 + item2
};
};
template<typename T>
T operator+(const T& lhs, const T&) {
return lhs;
}
int main() {
// error: call to non-'constexpr' function 'T operator+(const T&, const T&)
constexpr auto enumval = enum_container::item1 + enum_container::item2;
return 0;
}