C++ Logo

std-proposals

Advanced search

Unify overloading of relational operators with templated user-defined functions

From: tobi_at <tobi_at_[hidden]>
Date: Tue, 06 Jul 2021 11:01:35 +0200
The goal of this proposal is to unify the way operators can be
overloaded:
Currently all overloadable operators can be overloaded by template and
non-template functions with one exception: relational operators for an
enumeration can only be overloaded by non-template functions.

The standard allows to overload operators when at least one of its
arguments is (a reference to) a possible cv-qualified class or
enumeration type. To prevent ambiguities during overload resolution, a
user-defined operator hides a built-in operator when both have the same
parameter-type-list - but here exists the restriction in
[over.match.oper](3.3.4), that only non-template user-defined operators
do so.
The rationale for this exception seems to be preventing template
functions like

template<class T, class S>
unspecified-type operator @(T,S); // (1)

from hiding built-in operators, as this would introduce overloading
operators for built-in types through the backdoor.
For example in the following code

int i{};
char c{};
if( i @ d ) {
// ...
}

when not prohibited by [over.match.oper](3.3.4) the templated operator
would be selected.

Nevertheless, templated operators like (1) are called when at least one
of the parameter types is (a reference to) a class or an enumeration,
with one exception: a user-defined templated operator is NOT called when
T and S are the same enumeration type and @ is a relational operator.
(Yet for any enumeration E all relational operators can be overloaded by
non-template operators.)
The reason behind this is that [over.built](18) defines all relational
operators for enumerations, and as the user-defined operator is a
template, the built-in relational operator is not rejected (cf.
[over.match.oper](3.3.4)). Thus overload resolution selects the built-in
operator over the templated user-defined.

Please note that:
- overloading relational operators with (1) works when after stripping
cv-qualification and references, at least one of types T and S is a
class or enumeration type AND T and S are not the same enumeration type
- overloading operators for enumerations with template functions works
for all other overloadable operators, as the built-in versions, when
defined, invoke conversions to the underlying type

This leads to the funny situation that a user-defined operator is
selected over a built-in operators except when the following are ALL
true
- the operator is a relational operator
- the parameter-type-list is (E,E) where E is an enumeration type (of a
cv- qualified reference to an enumeration)
- the user-defined operator is a template

This unpleasant and rather unintuitive exception could be fixed, when
clause [over.match.oper](3.3.4) was replaced by a combination of it with
[over.oper.general](7):

[over.match.oper](3.3.4)
- do not have the same parameter-type-list as any non-member candidate
that <del>is not a function template specialization.</del><ins>has at
least one parameter whose type is a class, a reference to a class, an
enumeration, or a reference to an enumeration.</ins>

That way, all templated operators would also become "first class"
operator overloads, but overloading of operators for built-in types was
still prohibited.

Impact on existing code:
Of course, this would be a breaking change for overloading relational
operators for enumerations, but a quick search for templated overloads
of operator < of the form "template<class T> bool operator <" did not
show any match in the codebases of LLVM or Boost (1.75). Even more, if
there was a match in an existing codebase, it was probably meant to
overload the built-in operator.

Rationale:
This exception was discovered when implementing a library to
non-intrusively support bitwise-operations on flag-like enumerations.
Since for many enumerations and especially for flag-like enumerations
the underlying value of a (flag-)enumerator should not determine the
semantics of the program, it should be possible to disable or at least
static_assert when one of the operators <, >, <= or => is used. But
unfortunately, this is currently not possible with templated operators.
This change would remove an exception from a language and make
overloading of operators more uniform, more understandable and easier to
teach.
The bitwise-operations library can be found here:
https://github.com/tobias-loew/bitwise_operators

-- Tobias Loew

Received on 2021-07-06 04:01:40