Template class with implicit conversion and operator overload but not inline
Asked Answered
M

1

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.)

Mestee answered 4/12, 2021 at 23:28 Comment(0)
E
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);
}
Executor answered 4/12, 2021 at 23:49 Comment(9)
Does not work - "friend declaration declares a non-template function" and linker error for the operator.Mestee
@Mestee Which one doesn't work?Executor
The first one does not match the operator, the second one has the errors I mentioned.Mestee
@Mestee The first one works fine for me: godbolt.org/z/G5Go8494fExecutor
But doesn't work for 42 + Number<int>(42)Mestee
@Mestee Regarding the second one, you're right, I made a mistake. You have to add <>. I'll edit the answer.Executor
@Mestee 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.Executor
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.Mestee
@Mestee 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.Executor

© 2022 - 2024 — McMap. All rights reserved.