Function Template Overloading with Different Return Types

153 views Asked by At

Following code snippets are from Function template overloading.

How is it possible to overload functions/function templates with return type A<I+J> vs A<I-J>?

Did the page really mean that overload #1 & overload #2 compose valid function overload set?

Or, have I misunderstood the meaning of the page?

template<int I, int J>
A<I+J> f(A<I>, A<J>); // overload #1
 
template<int K, int L>
A<K+L> f(A<K>, A<L>); // same as #1
 
template<int I, int J>
A<I-J> f(A<I>, A<J>); // overload #2

I have tried the following in https://godbolt.org, which did not compile as expected with errors:

ASM generation compiler returned: 1
<source>: In function 'int main()':
<source>:48:24: error: call of overloaded 'func<1, 2>(A<1>, A<2>)' is ambiguous
   48 |     A<-1> a = func<1,2>(A<1>{}, A<2>{});
      |               ~~~~~~~~~^~~~~~~~~~~~~~~~
<source>:35:8: note: candidate: 'A<(I + J)> func(A<I>, A<J>) [with int I = 1; int J = 2]'
   35 | A<I+J> func(A<I>, A<J>) {
      |        ^~~~
<source>:41:8: note: candidate: 'A<(I - J)> func(A<I>, A<J>) [with int I = 1; int J = 2]'
   41 | A<I-J> func(A<I>, A<J>) {
      |        ^~~~
template <int>
struct A {
};

template <int I, int J>
A<I+J> func(A<I>, A<J>) {
    std::cout << "func1\n";
    return A<I+J>{};
}

template <int I, int J>
A<I-J> func(A<I>, A<J>) {
    std::cout << "func2\n";
    return A<I-J>{};
}

int main()
{
    A<-1> a = func<1,2>(A<1>{}, A<2>{});
    return 0;
}
2

There are 2 answers

0
The Dreams Wind On BEST ANSWER

Templates which differ by return types only can still have a well-formed overload resolution if you cast the function explicitly when using in the client code. This is the only known to me way to disambiguate such templates, however, so the application is quite narrow and inconvenient:

template<int I>
struct A {};

template<int I, int J>
A<I+J> f(A<I>, A<J>) {
    std::cout << "Overload 1" << std::endl;
    return A<I+J>{};
}

template<int I, int J>
A<I-J> f(A<I>, A<J>) {
    std::cout << "Overload 2" << std::endl;
    return A<I-J>{};
}

int main() {
    // well-formed
    auto a = static_cast<A<-1>(&)(A<1>, A<2>)>(f)(A<1>{}, A<2>{});
}
0
cpp On

Thanks a lot for everyone's information. With the help of reading all the comments, answers and some more testing, I am trying to give a consolidated answer for future reference.

To start with, I totally agree with user17732522 that we should look at this problem from 2 aspects.

  1. Non-template function/function template declaration:

    For non-template function declaration, return type is not taken into account. That's why following declarations will cause compilation error of ambiguating new declaration of int func()

    void func();
    
    int func();
    

    However, for function template declaration, return type is taken into account and that feature is useful as being pointed out by artyer for SFINAE on the return type where enable_if_t<...> is used.

  2. Non-template function/function template calling:

    As per The Dreams Wind's explantions,in case we would like to pick a specific definition of function template to call from an overload set, we either have to cast it to the specific type manually or by leveraging SFINAE as mentioned above.

    template <int>
    struct A {
    };
    
    template <int I, int J>
    A<I+J> func(A<I>, A<J>) {
        std::cout << "func1\n";
        return A<I+J>{};
    }
    
    template <int I, int J>
    A<I-J> func(A<I>, A<J>) {
        std::cout << "func2\n";
        return A<I-J>{};
    }
    
    int main()
    {
        A<-1> a = static_cast<A<-1>(&)(A<1>, A<2>)>(func)(A<1>{}, A<2>{});
        A<3> b = static_cast<A<3>(&)(A<1>, A<2>)>(func)(A<1>{}, A<2>{});
        return 0;
    }