'Why is a template with deduced return type not overloadable with other versions of it?

Why are the following two templates incompatible and can't be overloaded?

#include <vector>

template<typename T>
auto f(T t) { return t.size(); }
template<typename T>
auto f(T t) { return t.foobar(); }

int main() {
   f(std::vector<int>());   
}

I would think they are (more or less) equivalent with the following which compiles fine (as we cannot do decltype auto(t.size()) I can't give an exact equivalent without some noise..).

template<typename T>
auto f(T t) -> decltype(t.size() /* plus some decay */) { return t.size(); }

template<typename T>
auto f(T t) -> decltype(t.foobar() /* plus some decay */) { return t.foobar(); }

Clang and GCC complain main.cpp:6:16: error: redefinition of 'f' if I leave off the trailing return type, however.

(Note that I am not seeking for the place in the Standard which defines this behavior - which you may include in your answer too, if you wish - but for an explanation of why this behavior is desirable or status-quo).



Solution 1:[1]

I think that may be commitee miss but backstory I believe is the following:

  1. You cannot overload over the function return type. This means that in declaration

    template<typename T>
    auto f(T t) { return t.size(); }
    

    Value of auto is not interesting to compiler in fact until function instantiation. Obviously compiller does not add some SFINAE check to the function body to check if T::size exist as it does not in all other cases when T is used inside function body

  2. When generating overloads compiler will check if two function signatures are exact equivalent taking in mind all possible substitutions.

    In the first case then compiler will get smth like

    [template typename T] f(T)
    [template typename T] f(T)
    

    That are exact equivalent

    In the second case however as decltype specified explicitly it will be added to the template arguments so you'll get

    [template typename T, typename = typeof(T::size())] f(T)
    [template typename T, typename = typeof(T::size())] f(T)
    

    That are not exact equivalents obviously.

    So compiler will refuse the first case while second could be OK when substituting real type instead of T.

Solution 2:[2]

Looking at the symbols created by my compiler:

[tej@archivbox ~]$ cat test1.cc

#include <vector>

template<typename T>
auto JSchaubStackOverflow(T t) { return t.size(); }

// template<typename T>
// auto f(T t) { return t.foobar(); }

int do_something() {
       JSchaubStackOverflow(std::vector<int>());
       return 4;
}
[tej@archivbox ~]$ c++ -std=c++14 -pedantic test1.cc -c -o test1.o
[tej@archivbox ~]$ nm test1.o | grep JScha
0000000000000000 W _Z20JSchaubStackOverflowISt6vectorIiSaIiEEEDaT_
[tej@archivbox ~]$ nm -C test1.o | grep JScha
0000000000000000 W auto JSchaubStackOverflow<std::vector<int, std::allocator<int> > >(std::vector<int, std::allocator<int> >)
[tej@archivbox ~]$ cat test2.cc

#include <vector>

template<typename T>
auto JSchaubStackOverflow(T t) -> decltype(t.size() /* plus some decay */) { return t.size(); }

template<typename T>
auto JSchaubStackOverflow(T t) -> decltype(t.foobar() /* plus some decay */) { return t.foobar(); }
struct Metallica
{

    Metallica* foobar() const
    {
        return nullptr;
    }
};


int do_something() {
       JSchaubStackOverflow(std::vector<int>());
       JSchaubStackOverflow(Metallica());
       return 4;
}
[tej@archivbox ~]$ c++ -std=c++14 -pedantic test2.cc -c -o test2.o
[tej@archivbox ~]$ nm test2.o | grep JScha
0000000000000000 W _Z20JSchaubStackOverflowI9MetallicaEDTcldtfp_6foobarEET_
0000000000000000 W _Z20JSchaubStackOverflowISt6vectorIiSaIiEEEDTcldtfp_4sizeEET_
[tej@archivbox ~]$ nm -C test2.o | grep JScha
0000000000000000 W decltype (({parm#1}.foobar)()) JSchaubStackOverflow<Metallica>(Metallica)
0000000000000000 W decltype (({parm#1}.size)()) JSchaubStackOverflow<std::vector<int, std::allocator<int> > >(std::vector<int, std::allocator<int> >)

What you can see from this, is the decltype(whatever) can help us differentiate between symbols, it is part of the signature. But "auto" doesn't help us... So if vector had both a foobar, and size method, both overloads of JSchaubStackOverflow would be mangled as Z20JSchaubStackOverflowISt6vectorIiSaIiEEEDaT Now I'll leave to someone else to find to related section in ISO about signatures of template functions.

--EDIT-- I know it already has an accepted answer, but just for the record, here is a technical difficulty -- declarations without definitions:

[tej@archivbox ~]$ cat test2.cc

#include <vector>

template<typename T>
auto JSchaubStackOverflow(T t) -> decltype(t.size());

template<typename T>
auto JSchaubStackOverflow(T t) -> decltype(t.foobar());

struct Metallica
{

    Metallica* foobar() const
    {
        return nullptr;
    }
};


int do_something() {
       JSchaubStackOverflow(std::vector<int>());
       JSchaubStackOverflow(Metallica());
       return 4;
}
[tej@archivbox ~]$ c++ -std=c++14 -pedantic test2.cc -c -o test2.o
[tej@archivbox ~]$ nm -C test2.o | grep JScha
                 U decltype (({parm#1}.foobar)()) JSchaubStackOverflow<Metallica>(Metallica)
                 U decltype (({parm#1}.size)()) JSchaubStackOverflow<std::vector<int, std::allocator<int> > >(std::vector<int, std::allocator<int> >)

This means one can do the whole thing without function bodies. The template specializations would be given in another translation unit, but for this, the linker needs to find them... thus one can't overload on function body.

Solution 3:[3]

From cppreference.com:

Only the failures in the types and expressions in the immediate context of the function type or its template parameter types are SFINAE errors.

If the evaluation of a substituted type/expression causes a side-effect such as instantiation of some template specialization, generation of an implicitly-defined member function, etc, errors in those side-effects are treated as hard errors.

Your first declaration causes implicit substitution of the return type, and therefore does not adhere to SFINAE

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 TylerH