3

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.

max66
  • 65,235
  • 10
  • 71
  • 111
Etienne M
  • 604
  • 3
  • 11
  • Please do include the error message in the quesiton. The last version compiles (and links) fine here: https://godbolt.org/z/oW7TY5 Did you perhaps put the definition of the operator in a source file? Also I dont understand what you mean with "This code expands as" – 463035818_is_not_an_ai Jul 23 '20 at 11:08
  • maybe this https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file – 463035818_is_not_an_ai Jul 23 '20 at 11:08
  • I'm using compiler explorer too : https://godbolt.org/z/87a149 and I get the following error : undefined reference to `operator~(dummy::enumenum)' clang-10: error: linker command failed with exit code 1. Edit : I believe the reason you don't get a linkage error is that the link (https://godbolt.org/z/oW7TY5) you gave me does not use the option "Compile to binary" or the option "Run the compiled output". Thus it does not run the linker. – Etienne M Jul 23 '20 at 11:49
  • 1
    *what if I don't want to define the operator in the class body* - This seems like the most elegant solution in any case. Why not? (Didn't manage to solve your problem after much tinkering). – Benny K Jul 23 '20 at 12:08
  • @BennyK I generally keep my struct/class definition without method definitions. It's not mandatory in any case, just a matter of style. Anyway, while there is not a valid answer, I'll keep the version using friend method definition. – Etienne M Jul 23 '20 at 12:25
  • 1
    If it's a matter of style, you can always call a method defined elsewhere from the friend definition (although you might have to add some forward declarations). P.S. The *naive* implementation can work if you use `template T operator~(T a)`, but I couldn't find a way to check whether `T` is of type `dummy::enumenum` for some `U`. – Benny K Jul 23 '20 at 12:35
  • Then for now, I'll keep using the "friend" version, the `template T operator~(T a)` could be dangerous. – Etienne M Jul 23 '20 at 13:08
  • Not *could*. Is. Extremely. I thought it may be possible to use SFINAE or concepts (c++20) to limit this, but no such luck. – Benny K Jul 23 '20 at 14:00

1 Answers1

2

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.

max66
  • 65,235
  • 10
  • 71
  • 111
  • Hi, and thanks for your answer. Although the idea using the templated friend declaration sound correct it does not seem to work. As matter of fact, I believe that the line `auto a = ~dummy::a;` would expand as `int a = ~static_cast(dummy::a);`. This can be checked if you replace the `auto` by `dummy`, which won't compile (no viable conversion from 'int' to 'dummy | using clang 10). Your templated friend declaration version example, except for the `auto` : https://godbolt.org/z/M57W4h . – Etienne M Jul 23 '20 at 13:52
  • The first solution cannot work because the signatures are different. However, it doesn't seem to work even if they aren't: https://godbolt.org/z/zj66xb – Benny K Jul 23 '20 at 13:55
  • @EtienneM - You're right... well, the intended value of `a` is `dummy::enumenum`, not `dummy`, but you're right: you get `int`. I've forgotten the template deduction. Answer modified. – max66 Jul 23 '20 at 14:56
  • @BennyK - You're right: different signatures (corrected) and, anyway, doesn't works. You've seen the problem before than me: template deduction. Answer modified. – max66 Jul 23 '20 at 14:57
  • @max66 I apologize for the mistake in the comment, unfortunately I can't edit it... – Etienne M Jul 23 '20 at 15:21
  • @EtienneM - No problem: my mistake was worse. – max66 Jul 23 '20 at 16:42