4
template <typename T>
int get_num(const T&)
{
    return 42;
}

struct Foo
{
    int i = get_num(*this);
};

int get_num(const Foo&)
{
    return 23;
}

int main()
{
    std::cout << Foo().i << std::endl; // MinGW64 - 42, MSVC - 23
    return 0;
}

MSVC chooses non-template get_num "overload". It will successfully link even if the template is only forward-declared. MinGW64 will choose the default template implementation. Will fail to link if there is no definition for the template.

Who is right and who is wrong? Whad does the standard say?


However this version yields the same results for both compilers... Why does it not go to infinite recursion?

template <typename T>
int get_num(const T& t)
{
    std::cout << "Template version called" << std::endl;
    return get_num(t);
}

struct Foo
{
    int i = get_num(*this);
};

int get_num(const Foo&)
{
    std::cout << "Non-Template version called" << std::endl;
    return 23;
}

MSVC output:

Non-Template version called
23

MinGW64 Output:

Template version called
Non-Template version called
23

I think ADL is involved when using MSVC.

Sergey Kolesnik
  • 3,009
  • 1
  • 8
  • 28

1 Answers1

2

For the first sample, MinGW is correct and MSVC is incorrect: the get_num call can only consider the function template.

This is a question of overload resolution, so I'll start in clause [over]. get_num(*this) is a plain function call expression, so [over.call.func] applies:

In unqualified function calls, the name is not qualified by an -> or . operator and has the more general form of a primary-expression. The name is looked up in the context of the function call following the normal rules for name lookup in function calls. The function declarations found by that lookup constitute the set of candidate functions.

"Name lookup" is discussed in section [basic.lookup]. Paragraph 2:

A name "looked up in the context of an expression" is looked up as an unqualified name in the scope where the expression is found.

One complication here is that the get_num(*this) default member initializer expression doesn't get used by anything until the default constructor of Foo is implicitly defined by its odr-use in main. But the lookup is determined from the code location of the expression itself, no matter that it's used in the process of that implicit definition.

For the second code sample, MinGW is again correct: the apparently recursive call inside the template definition actually calls the non-template function.

This is a result of "two phase lookup" for dependent function calls, described in section [temp.dep.res]. Briefly, since the type of t depends on a template parameter, the name get_num in the expression get_num(t) is considered a dependent name. So for each instantiation of the function template, it gets two ways of finding candidates for overload resolution: the ordinary immediate way which finds the get_num function template, plus another lookup from the point of instantiation. The specialization get_num<Foo> has point of instantiation right after the main() definition, so overload resolution is able to find the non-template from the instantiation context, and the non-template wins overload resolution.

(Argument-dependent lookup is a related tangent issue to this second point. ADL applies to declarations from the instantiation context but not declarations from the definition context. But it's not directly a reason for the behaviors in either example program.)

None of this has changed significantly between C++14 and the latest draft, as far as I can see.

aschepler
  • 70,891
  • 9
  • 107
  • 161
  • So the second example would not compile if the instantiation point for `Foo` was before `get_num(const Foo&)` declaration? – Sergey Kolesnik Nov 14 '21 at 20:37
  • It would be ill-formed no diagnostic required, since the end of the translation unit (or C++20 module) is another instantiation point which would give the same specialization different meaning ([\[temp.point\]/7](https://timsong-cpp.github.io/cppwp/temp.point#7)). Probably most compilers will compile it, but you'll have to guess what will actually happen. – aschepler Nov 14 '21 at 20:41
  • _The specialization `get_num` has point of instantiation right after the `main()` definition_ Why? – Language Lawyer Nov 14 '21 at 20:48
  • @LanguageLawyer [\[temp.point\]/1](https://timsong-cpp.github.io/cppwp/temp.point#1) When a function template instantiation is not caused by another template instantiation, ... "the point of instantiation for such a specialization immediately follows the namespace scope declaration or definition that refers to the specialization". – aschepler Nov 14 '21 at 20:52
  • But `Foo::Foo()` and not `main()` refers to `get_num`. – Language Lawyer Nov 14 '21 at 20:53
  • @LanguageLawyer Hmm, good point. But is the implicit definition a namespace scope declaration? If so, I doubt it has a defined point within the lexical syntax. – aschepler Nov 14 '21 at 20:59
  • The best candidate for declaration which refers to `get_num` specialization is `struct Foo`. – Language Lawyer Nov 15 '21 at 08:04
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/239240/discussion-between-aschepler-and-language-lawyer). – aschepler Nov 15 '21 at 14:30