2

I have written a simple Flags class, but face a problem with my operator definition. It seems like I'm relying on some nonexistent implicit conversion rule.

enum class SomeEnum { ONE, TWO, THREE };

template<typename Enum>
class Flags {
    Flags(const Flags& f) { };
    Flags(Enum e) { };

    Flags& operator |=(Flags<Enum> f) {
        //...;
        return *this;
    }
};

template<typename Enum>
Flags<Enum> operator |(Enum a, Flags<Enum> b) { return b | a; }

template<typename Enum>
Flags<Enum> operator |(Flags<Enum> a, Enum b) { return a |= b; }

int main() {
    Flags<SomeEnum> flags = SomeEnum::ONE | SomeEnum::TWO;
    return 0;
}

While compilation I get this error:

implicit.cpp: In function ‘int main()’:  
implicit.cpp:26:40: error: no match for ‘operator|’ (operand types are ‘SomeEnum’ and ‘SomeEnum’)  
  Flags<SomeEnum> flags = SomeEnum::ONE | SomeEnum::TWO;

My understanding was, that one of the SomeEnums is implicitly converted into a Flags<Enum>, and then passed to the correct operator. What rule am I missing?

EDIT:

I have looked at https://stackoverflow.com/questions/9787593, but the proposed solution (nonmember-friend operator) did not solve my problem. I did remove the global definitions and add these members:

friend Flags operator |(Enum a, Flags b) { return b | a; }
friend Flags operator |(Flags a, Enum b) { return a |= b; }

But the error is still the same (live demo).

nyronium
  • 1,258
  • 2
  • 11
  • 25
  • 1
    Possible duplicate of [C++ implicit type conversion with template](https://stackoverflow.com/questions/9787593/c-implicit-type-conversion-with-template) – Corristo Nov 10 '17 at 21:01
  • I am unsure if it is exact duplicate or not (would like someone else to look at it as well), but related: https://stackoverflow.com/questions/32578638/how-to-use-c11-enum-class-for-flags – Algirdas Preidžius Nov 10 '17 at 21:03
  • I didn't know what exactly to search for, as googling for `implicit conversion does not work as expected` didn't give any good results (obviously) – nyronium Nov 10 '17 at 21:07
  • @AlgirdasPreidžius I don't see anything in common with that question. He has another problem with a `Flags` class (which is not even templated; and he uses operator | with the same type for a and b) – nyronium Nov 10 '17 at 21:12
  • 1
    @nyronium Yet, judging from the error you get - your templated class doesn't even come into question. The error comes for compiler not finding a suitable match for `operator|` where both operands are of type `SomeEnum`, hence - my suggestion. – Algirdas Preidžius Nov 10 '17 at 21:20
  • @Corristo I have looked at it and edited my question accordingly. Thanks for the suggestion – nyronium Nov 10 '17 at 21:20
  • @AlgirdasPreidžius But unfortunately I can't apply the solution from the link you posted to my problem - or at least I don't see how. – nyronium Nov 10 '17 at 21:21
  • @nyronium Why can't you write overloaded operators for `SomeEnum` class? That's essentially what `Flags` enum is in the linked question, compared to yours. Differing names != differing problem. – Algirdas Preidžius Nov 10 '17 at 21:26
  • @AlgirdasPreidžius I can't overload any operators for `SomeEnum` as the Flags class should be working for any enum. Its part of a library – nyronium Nov 10 '17 at 21:29

1 Answers1

3

In order to know that SomeEnum is convertible to Flags<SomeEnum> it would already have to have deduced the Enum template argument as SomeEnum, but it can't deduce that from the arguments because neither of them matches Flags<Enum>.

i.e. the template argument deduction has to happen before conversions to another type can be checked.

You can adjust the functions so that only one argument participates in argument deduction:

template<typename T> struct nondeduced { using type = T; }

template<typename Enum>
Flags<Enum> operator |(Enum a, Flags<typename nondeduced<Enum>::type> b)

or equivalently:

template<typename E> struct FlagType { using type = Flags<E>; }

template<typename Enum>
Flags<Enum> operator |(Enum a, typename FlagType<Enum>::type b);

This uses the Enum template argument in a non-deduced context, so only the other argument is used for deduction.

But after doing that you now have ambiguous overloads, because there's no way for the compiler to know whether you want the first argument to be converted, or the second argument.

You need to add an overload that takes two SomeEnum types, and performs the conversions to Flags<SomeEnum> explicitly. If an enumeration type is supposed to be used with operator| then it should define that operator itself.

Another option that doesn't involve changing the enumeration types would be to add a helper that turns an enumerator into a Flags object:

template<typename Enum>
inline Flags<Enum> flags(Enum e) { return Flags<Enum>(e); }

then you can say SomeEnum::TWO | flags(SomeEnum::TWO) which avoids the ambiguity.

As a stylistic point, don't use ALL_CAPS for enumerators.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Is there a way to prioritize one of the ambiguous overloads? Because `Flags` is part of a library I now have to create `template operator |(Enum a, Enum b)` which might break other enums which are should not be used for Flags. – nyronium Nov 10 '17 at 21:38
  • @nyronium that sounds like a recipe for disaster to me, having `a | b` do something different based on a subtle context change – M.M Nov 10 '17 at 21:44
  • @M.M well probably... Though it isn't a theoretical problem, just a c++ one. Maybe one day. – nyronium Nov 10 '17 at 21:49
  • @M.M Actually, if I apply this solution to only one of the two operators, I would have exactly this behavior. If a user defined operator Enum | Enum exists, it takes precedence and there is no ambiguity in any case. – nyronium Nov 10 '17 at 22:01
  • @jonathan-wakely, would you mind elaborating on this approach for completeness? – nyronium Nov 10 '17 at 22:06
  • What is "this solution" and "this approach" referring to? I didn't say to add a templated `operator|` but to add a (non-template) `operator|(SomeEnum, SomeEnum)`. I've updated the answer. – Jonathan Wakely Nov 10 '17 at 23:51
  • @JonathanWakely Sorry for being imprecise. I was talking about applying your `Flags::type>` to only one of the two global operators. Then there is no ambiguity and no need for `operator|(SomeEnum, SomeEnum)`. If the user then defines such an operator, it will even take precedence which is perfect. – nyronium Nov 11 '17 at 09:44
  • This answer would be improved by mentioning the ADL/Koenig lookup problem and which namespace this tooling has to be in (the `Enum` namespace) – Yakk - Adam Nevraumont Nov 13 '17 at 20:51