简体   繁体   中英

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM