5

While writing a CRTP template that enables classes to provide overloads for operator+ based on template arguments, I found that an in-class friend operator doesn't seem to participate in overload resolution if none of it's arguments is of the type of the class it was defined in.

Boiled down:

enum class FooValueT{
    zero, one, two
};

class Foo{
    FooValueT val_;
public:
    Foo(FooValueT x) : val_(x){};

    Foo& operator+=(Foo other){
        val_ = (FooValueT)((int)val_ + (int)other.val_);
        return *this;
    }

    //overload for Foo+Foo, FooValueT+Foo and Foo+FooValueT
    friend Foo operator+(Foo lhs, Foo rhs){
        Foo ret = lhs;
        return ret += lhs;
    }

    //explicit overload for FooValueT+FooValueT
    friend Foo operator+(FooValueT lhs, FooValueT rhs){
        return (Foo)lhs + (Foo)rhs;
    }
};

Looks a bit excessive, but is necessary since Foo my = FooValueT::one + FooValueT::zero; should be a valid expression and if none of the arguments has class-type, they are not implicitly converted, as explained in this answer to a previous question of mine.

Despite all this effort, the following code does not compile:

int main(int argc, char* argv[])
{
    Foo my = FooValueT::zero;
    my += FooValueT::one;
    my = Foo(FooValueT::zero) + FooValueT::two;
    my = FooValueT::zero + Foo(FooValueT::two);
    my = FooValueT::zero + FooValueT::two; //error C2676
    return 0;
}

The error message is:

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

This problem resolves once I either move the operator out of the class completely, or declare it as a friend but define it outside the class. Neither of both seem to be viable options when Foo is a template class that is to be derived from.

As far as I know, the above in-class friend definition of operator+(ValueT,ValueT) should create a free function, just as this definition would:

class Foo{
/*All the stuff you saw previously*/
    friend Foo operator+(FooValueT lhs, FooValueT rhs);
};

Foo operator+(FooValueT lhs, FooValueT rhs){
    return (Foo)lhs + (Foo)rhs;
}

Where am I going wrong here? Does in-class friend definition of functions change the rules of overload resolution compared to regular free friend functions?

Community
  • 1
  • 1
iFreilicht
  • 13,271
  • 9
  • 43
  • 74
  • @JoachimPileborg even if that functions arguments and return type are dependent on template parameters and don't contain any notion of the template class itself? If yes, what is the syntax for that? EDIT: Nevermind, found it, and it seems helpful. – iFreilicht Dec 09 '14 at 10:23
  • Moving the enum type inside the class also fixes it, but I doubt that it helps here. – T.C. Dec 09 '14 at 10:50
  • Try adding 'using Foo::operator+' outside the scope of the class – Asaf Dec 09 '14 at 10:52
  • @T.C. you are correct, it doesn't help in my case. But it is interesting nonetheless. – iFreilicht Dec 09 '14 at 13:05
  • @Asaf this doesn't work at all, why should it? `operator+` is not a member of `Foo`, the qualified name `Foo::operator+` is not valid. – iFreilicht Dec 09 '14 at 16:43
  • I somehow missed the 'friend' there. Sorry. – Asaf Dec 09 '14 at 19:57

1 Answers1

5

n3376 11.3/6-7

A function can be defined in a friend declaration of a class if and only if the class is a non-local class (9.8), the function name is unqualified, and the function has namespace scope.

Such a function is implicitly inline. A friend function defined in a class is in the (lexical) scope of the class in which it is defined. A friend function defined outside the class is not (3.4.1).

In your case operator is in class-scope and when you are trying to call this operator, ADL will not try to find operator in class, since neither argument has type of this class. Just write free function (or friend with declaration not in class).

Seems like you cannot do something like this, problem with declaration of friend function in class is that function will be in class-scope, but you cannot declare this function to be free friend function, since compiler cannot infer return-type parameter.

ForEveR
  • 55,233
  • 2
  • 119
  • 133
  • 2
    7.3.1.2/3 is the more relevant quote. – T.C. Dec 09 '14 at 10:44
  • 1
    To cite myself: "This problem resolves once I either move the operator out of the class completely, or declare it as a friend but define it outside the class. Neither of both seem to be viable options when `Foo` is a template class that is to be derived from." So while this is a correct answer, it is not a solution to my problem. – iFreilicht Dec 09 '14 at 10:45
  • @iFreilicht what is problem with template class? Can you elaborate it? With simple minimal example. – ForEveR Dec 09 '14 at 10:49
  • `template class ImplAdd{ friend Type operator+(ValueT, ValueT); }; //what do I write here?` When trying to call `operator+(FooValueT, FooValueT)` after deriving `Foo` from `ImplAdd` like this `class Foo : ImplAdd{};`, the template arguments for ImplAdd can't be deduced from the call. – iFreilicht Dec 09 '14 at 13:03
  • Thanks for the edit, but this neither works in MSVC (the error message is still the same), nor do I understand why this compiles in gcc+. Doesn't this contradict the exact quote you posted? – iFreilicht Dec 10 '14 at 20:36
  • @iFreilicht you are right, my answer is not correct, clang cannot compile this too. – ForEveR Dec 11 '14 at 08:13