The problem
Given the following piece of code :
template <typename T>
struct dummy {
enum enumenum { a = 1, b = 2, c = 4 };
};
int main() {
// I want this line to expands as :
// dummy<double>::enumenum a = operator~(dummy<double>::a);
auto a = ~dummy<double>::a;
}
How do you overload operators on enumenum
?
I'm using std C++14.
What I tried
A naive implementation:
template <typename T>
typename dummy<T>::enumenum
operator~(typename dummy<T>::enumenum a) {
return static_cast<typename dummy<T>::enumenum>(operator~(a));
}
Unfortunately the line in question expands as:
int a = ~static_cast<int>(dummy<double>::a);
Which means that the operator was not used (this is the default behavior).
Is it because ADL could not find the right operator~()
in the struct namespace (is that even a thing ?) ?
Then I tried: (note the friend
)
template <typename T>
struct dummy {
enum enumenum { a, b, c };
friend enumenum operator~(enumenum a) {
return static_cast<enumenum>(~a);
}
};
This actually works and expands as:
template <>
struct dummy<double> {
enum enumenum {
a = static_cast<unsigned int>(1),
b = static_cast<unsigned int>(2),
c = static_cast<unsigned int>(4)
};
friend inline dummy<double>::enumenum operator~(dummy<double>::enumenum a) {
return static_cast<dummy<double>::enumenum>(operator~(a));
}
};
int main()
{
dummy<double>::enumenum a = operator~(dummy<double>::a);
}
This is the behavior I want. Except, what if I don't want to define the operator in the class body.
So I tried :
template <typename T>
struct dummy {
enum enumenum { a = 1, b = 2, c = 4 };
// if inline : inline function 'operator~' is not defined [-Wundefined-inline]
// and adding inline to the template below does not help
friend enumenum operator~(enumenum a);
};
template <typename T>
typename dummy<T>::enumenum
operator~(typename dummy<T>::enumenum a) {
return static_cast<typename dummy<T>::enumenum>(~a);
}
int main() {
auto a = ~dummy<double>::a;
}
The code above expands as:
template<>
struct dummy<double>
{
enum enumenum
{
a = static_cast<unsigned int>(1),
b = static_cast<unsigned int>(2),
c = static_cast<unsigned int>(4)
};
friend dummy<double>::enumenum operator~(dummy<double>::enumenum a);
};
int main()
{
dummy<double>::enumenum a = operator~(dummy<double>::a);
}
This compiles, but does not link! Edit: I believe it does not link because the template is not instantiated thus failing at link time (similarly to the naive implementation above).
Conclusion
Even though I somehow found a way to achieve what I wanted, what if I don't want to define the operator inside the class definition.
Thanks in advance.
This compiles, but does not link!
Compile but doesn't link because you declare a non-template operator (it's inside a template struct but isn't a template function)
friend enumenum operator~(enumenum a);
and you define a template one
template <typename T>
typename dummy<T>::enumenum
operator~(typename dummy<T>::enumenum a) {
return static_cast<typename dummy<T>::enumenum>(~a);
}
and a template definition can't match a non-template declaration.
You could try to declare the function as a template one
template <typename T>
struct dummy
{
enum enumenum { a = 1, b = 2, c = 4 };
template <typename U>
friend typename dummy<U>::enumenum
operator~ (typename dummy<U>::enumenum const & a);
};
template <typename T>
typename dummy<T>::enumenum
operator~ (typename dummy<T>::enumenum const & a)
{ return static_cast<typename dummy<T>::enumenum>(~a); }
int main ()
{
auto a = ~dummy<double>::a;
}
but, unfortunately, this compile but, calling the ~
operator as follows
~dummy<double>::a;
isn't called the template function because the template parameter T
can't be deduced because is in not-deduced-context (before the last ::
), as pointed by Benny K in a comment.
So, instead the template operator~()
, the dummy<double>::a
is converted to int
and the ~
operator for int
is applied (with type result int
).
You can verify this point explicitly calling a function operator~()
auto a = operator~(dummy<double>::a);
You should get a "no matching function for call to 'operator~'" error (with note "note: candidate template ignored: couldn't infer template argument 'T'") or something similar.
To make this solution works, you have to explicate the type of the class, to avoid the template deduction
auto a = operator~<double>(dummy<double>::a);
and, now, you can verify that a
is a dummy<double>::enumenum
static_assert( std::is_same<decltype(a), dummy<double>::enumenum>::value, "!" );
But, obviously, this isn't a satisfactory solution (and very dangerous, if you forget to avoid the simple use of ~
).
Otherwise you can define the operator as non-template
template <typename T>
struct dummy
{
enum enumenum { a = 1, b = 2, c = 4 };
friend enumenum operator~ (enumenum const & a);
};
dummy<double>::enumenum
operator~(dummy<double>::enumenum const & a)
{ return static_cast<dummy<double>::enumenum>(~a); }
int main ()
{
auto a = ~dummy<double>::a;
}
but you have to define a different operator for every dummy<T>
type.
IMHO the most simple, safe and elegant solution is your working one: declare/define the operator inside the struct.