Search code examples
c++templatesoperator-overloadingc++14friend-function

Operator overloading on an enum nested in a class


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.


Solution

  • 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.