0

Based on the answer in Implicit conversion when overloading operators for template classes I was able to write the following code that works perfectly fine (simplified example):

namespace my_library {

template <typename T>
struct Number {
    T n;

    inline Number(T n) : n(n) { }

    friend Number<T> operator+(const Number<T> &a, const Number<T> &b) {
        return Number<T>(a.n + b.n);
    }
};

}

int main() {
    return (int) (4 + my_library::Number<double>(3)).n; // returns 7
}

All I want to do is make it so that operator+ is not inlined within the definition of Number (but stays in the header file) while everything still works the same way - see the expression in the main function. Note that it requires that the integer 4 gets implicitly converted to double and then to Number<double>. I followed a comment linking to Binary operator overloading on a templated class but that solution did not work - the overloaded operator is no longer matched. Is there any way to make this work and have the body of the operator outside the struct definition? (Without complicating the operator interface or adding more overloads - in those cases I'd rather just keep it inlined.)

Detheroc
  • 1,833
  • 15
  • 19

1 Answers1

0

You can do it like this:

template <typename T>
struct Number {
    // ...
    template <class U>
    friend Number<U> operator+(const Number<U> &a, const Number<U> &b);
};

template <typename U>
Number<U> operator+(const Number<U> &a, const Number<U> &b) {
    return Number<U>(a.n + b.n);
}

In this example, operator+ will be a friend of all Number specializations even when the types don't match. This might be a broader friendship than you intended, but is the simplest way to achieve your objective of moving the definition of operator+ outside the definition of Number. Note that when a friend is not defined inline, you lose the benefit of having it as a "hidden friend". (A hidden friend can only be found via argument-dependent lookup, which means that the compiler will typically not have to consider it as a candidate function in most cases where it is not intended to be used.)

If you want each specialization of operator+ to only be a friend of one particular Number specialization where the types match, it is more complicated. You have to forward-declare the operator+ template in order to prevent the friend declaration from declaring a non-template function, and you also have to forward-declare Number so that it can be used in the operator+ forward declaration:

template <typename T> struct Number;

template <typename T>
Number<T> operator+(const Number<T>&, const Number<T>&);

template <typename T>
struct Number {
    // ...
    friend Number<T> operator+<>(const Number<T>&, const Number<T>&);
};

template <typename T>
Number<T> operator+(const Number<T> &a, const Number<T> &b) {
    return Number<T>(a.n + b.n);
}
Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • Does not work - "friend declaration declares a non-template function" and linker error for the operator. – Detheroc Dec 04 '21 at 23:52
  • @Detheroc Which one doesn't work? – Brian Bi Dec 04 '21 at 23:53
  • The first one does not match the operator, the second one has the errors I mentioned. – Detheroc Dec 04 '21 at 23:54
  • @Detheroc The first one works fine for me: https://godbolt.org/z/G5Go8494f – Brian Bi Dec 04 '21 at 23:57
  • 1
    But doesn't work for `42 + Number(42)` – Detheroc Dec 04 '21 at 23:58
  • @Detheroc Regarding the second one, you're right, I made a mistake. You have to add `<>`. I'll edit the answer. – Brian Bi Dec 04 '21 at 23:59
  • @Detheroc That's true. It won't work in that case because template argument deduction doesn't consider implicit conversions. To fix this, you can add more overloads. You can't perfectly replicate the code in your original post with out-of-line definitions, though, because there is no way to provide a single definition for all of the possible `operator+`s that could be instantiated. – Brian Bi Dec 05 '21 at 00:02
  • But why can't the exact same operator be only declared inline but defined out-of-line? I thought you could always choose either of the two ways without effect on semantic functionality. – Detheroc Dec 05 '21 at 00:10
  • 1
    @Detheroc The case of friend functions is special. It can't be done because there's no syntax for it, and the reason why the usual syntax you'd use to define a template out of line doesn't work is that the `operator+` in your example isn't actually a template; it's just one separate non-template function that gets generated each time `Number` gets instantiated with a different `T`. – Brian Bi Dec 05 '21 at 00:13