3

I am using CRTP to provide template-argument dependent addition of functions to a class, in this case the addition of operator + and operator +=, using the template class ImplAdd. For the former, implicit conversions should be performed on both arguments, which means I have to use an in-class friend operator like this:

template<class Type, bool active>
struct ImplAdd{
    virtual int get_val_() const = 0;
    virtual void set_val_(int) = 0;
};

//if activated is true, the operators + and += will be defined
template<class Type>
class ImplAdd < Type, true > {
    virtual int get_val_() const = 0;
    virtual void set_val_(int) = 0;
    Type* this_(){ return (Type*)this; }
public:
    Type& operator +=(const Type& x){
        set_val_(get_val_() + x.get_val_());
        return *this_();
    }

    //This should enable conversions on the lefthand argument
    friend Type& operator+(const Type& lhs, const Type& rhs){
        Type ret = lhs;
        return ret += rhs;
    }
};

This is needed because classes that actually inherit from ImplAdd define constant values and a unique value type for those constants, much like a scoped enum.

//by using true as the template argument, the operators + and += will be defined
class MyEnum : public ImplAdd<MyEnum, true>{
    int get_val_() const override{
        return (int)value;
    }
    void set_val_(int v) override{
        value = (ValueT)v;
    }
public:
    enum class ValueT{
        zero, one, two
    };
private:
    typedef  int UnderlyingT;
    ValueT value;
public:
    static const ValueT zero = ValueT::zero;
    static const ValueT one = ValueT::one;
    static const ValueT two = ValueT::two;
    MyEnum(ValueT x) : value(x){}
    MyEnum(const MyEnum& other) : value(other.value){}
};

From my perspective, the following code should now easily compile, but it doesn't.

int main(int argc, char* argv[])
{
    MyEnum my = MyEnum::zero;                //works
    my += MyEnum::one;                       //works
    my = MyEnum(MyEnum::zero) + MyEnum::two; //works
    my = MyEnum::zero + MyEnum(MyEnum::two); //ERROR C2676
    my = MyEnum::zero + MyEnum::two;         //ERROR C2676
    MyEnum my2 = my + my;                    //works
    return 0;
}

For both lines marked with C2676, the following error message is printed:

error C2676: binary '+' : 'const MyEnum::ValueT' does not define this operator or a conversion to a type acceptable to the predefined operator

What am I doing wrong? Isn't the use of defining an operator as an in-class friend the common way to enable implicit conversion on both arguments? If not, how can I do it in this case?

iFreilicht
  • 13,271
  • 9
  • 43
  • 74

1 Answers1

3

§13.3.1.2 [over.match.oper]/p3 (emphasis added):

for a binary operator @ with a left operand of a type whose cv-unqualified version is T1 and a right operand of a type whose cv-unqualified version is T2, three sets of candidate functions, designated member candidates, nonmember candidates and built-in candidates, are constructed as follows:

  • [...]
  • The set of non-member candidates is the result of the unqualified lookup of operator@ in the context of the expression according to the usual rules for name lookup in unqualified function calls (3.4.2) except that all member functions are ignored. However, if no operand has a class type, only those non-member functions in the lookup set that have a first parameter of type T1 or “reference to (possibly cv-qualified) T1”, when T1 is an enumeration type, or (if there is a right operand) a second parameter of type T2 or “reference to (possibly cv-qualified) T2”, when T2 is an enumeration type, are candidate functions.
  • [...]

In plainer English, if neither operand has a class type, then you need to have an exact match on an enumeration operand for the overload to be considered, which is why my = MyEnum::zero + MyEnum::two; does not work. my = MyEnum::zero + MyEnum(MyEnum::two);, oddly enough, compiles in GCC but not Clang. I can't find anything that makes it not legal, so I'm suspecting that this might be a compiler bug.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • While this is certainly the right answer to the question why this doesn't work, do you have any idea what a possible solution might be? – iFreilicht Dec 08 '14 at 21:21