Search code examples
c++templatesgccvisual-c++argument-dependent-lookup

Where is this template function generated? Can compile by g++ but not in visual studio


The following code can not compile in my visual studio 2019. But if I delete the first overloading of >> it would compile.

The code can compile by g++ which confused me. I guess it is about different locations where the template function is generated by the compiler?

Error message: error C2679: binary '>>': no operator found which takes a right-hand operand of type 'std::vector<int,std::allocator<_T>>'

#include <iostream>
#include <vector>
typedef std::vector<int> Mon;  // ordered
typedef std::vector<Mon> Poly;  // ordered

class A {};

// It would compile successfuly if this function is removed
std::istream& operator>>(std::istream& sin, A& a)
{
    return sin;
}

template <typename Container>
void load(std::istream& sin, Container& cont)
{
    typename Container::value_type n;
    sin >> n;
}

std::istream& operator>>(std::istream& sin, Mon& mon)
{
    load(sin, mon);
    return sin;
}

std::istream& operator>>(std::istream& sin, Poly& poly)
{
    load(sin, poly);
    return sin;
}

int main()
{
    return 0;
}

Solution

  • The underlying problem is that this function signature in global namespace:

    std::istream& operator>>(std::istream& sin, std::vector<int>& mon);
    

    cannot be found by argument-dependent lookup. Since all of the arguments are in std, the ADL only searches std and not the global namespace.
    To avoid this sort of problem you can follow a rule of thumb: don't overload operators in such a way that they will not be found by ADL. (Corollary: you shouldn't try to make vector<Foo> v; cin >> v; work).


    Firstly, note that the syntax sin >> n translates to performing both operator>>(sin, n) and sin.operator>>(n) and combining all results, as described in full here.

    The code in the question is quite similar to this question and I will summarize the findings of the top answer there.

    For this function:

    template <typename Container>
    void load(std::istream& sin, Container& cont)
    {
        typename Container::value_type n;
        sin >> n;
    }
    

    specifically when the lookup of operator>>(sin, n) happens, the operator>> is a dependent name because it's the name of a function call whose argument types depend on the template parameter.

    When name lookup is applied to a dependent function name (ref: [temp.dep.candidate]) the rules are:

    1. Any function declarations visible at the point of definition of the template are considered.
    2. Any function declarations that would be found by ADL at the point of instantiation are considered.
    3. If there are extern functions defined elsewhere in the program that would have been found by ADL if they had a visible declaration at the point of instantiation, and these extra declarations would have affected the overload resolution, then the program has undefined behaviour (no diagnostic required).

    (NOTE: My first version of this answer incorrectly quoted Rule 3 and so drew the wrong conclusion):

    So the lookup of sin >> n instantiated from the call load(sin, mon); succeeds because the member function std::istream::operator>>(int&) is found. (The search also found the A& version but overload resolution selects the member function).

    The problem arises with the lookup of sin >> n instantiated by load(sin, poly);.

    According to Rule 1, operator>>(std::istream&, A&) is found. (This function would later be discarded by overload resolution, but at this stage we are just performing name lookup).

    According to Rule 2, the ADL namespace list is: std. So this step will find std::operator>> (various overloads), but not ::operator>>(istream&, Mon&); since that is not in namespace std.

    Rule 3 doesn't apply since there aren't any overloads in namespace std that would accept a Mon.

    So the correct behaviour is:

    • The code as posted should fail compilation due to no match for sin >> n when instantiating load(sin, poly);.
    • With the line marked It would compile successfully if this function is removed there should actually be no difference; the code should still fail compilation for the same reason.

    CONCLUSION: It appears to me that:

    • clang 8.0.0 behaves correctly.
    • msvc correctly rejects the posted code, but incorrectly accepts the modified version.
    • gcc 9.1.0 incorrectly accepts both versions;

    I note that if we change operator>> to bar and sin >> n to bar(sin, n); then gcc and msvc correctly reject both versions. gcc even gives a very similar error message to clang.

    So I conjecture that the bug might be an incorrect application of the overloaded operator name lookup rules -- which differ slightly to non-operator names but not in any way pertaining to this code example.

    For an in-depth writeup of the rationale for these rules and the behaviour of MSVC, see this excellent article.