0

I have a case where lookup and overload resolution behaves differently:

  • for user-defined class vs built-in types vs std::string
  • for direct call vs function pointer call

I cannot figure what exact parts of the standard justify these differences.

Consider the following C++11 code:

#include <iostream>
#include <string>

using namespace std;

struct Test1 {};
struct Test2 {};

template<typename T>
int f(T t) { return 0; }

int f(Test1 t) { return 10; }
int f(int y) { return 20; }

template<typename T>
int use1() { return f(T()); }

template<typename T>
int use2() { auto fp = static_cast<int(*)(T)>(&f); return (*fp)(T()); }

int f(Test2 t) { return 30; }
int f(string s) { return 40; }
int f(double y) { return 50; }

int main() {
    cout << "use1<float>:  " << use1<float>()  << endl;
    cout << "use1<Test1>:  " << use1<Test1>()  << endl;
    cout << "use1<int>:    " << use1<int>()    << endl;
    cout << "use1<Test2>:  " << use1<Test2>()  << endl;
    cout << "use1<string>: " << use1<string>() << endl;
    cout << "use1<double>: " << use1<double>() << endl;
    cout << endl;
    cout << "use2<float>:  " << use2<float>()  << endl;
    cout << "use2<Test1>:  " << use2<Test1>()  << endl;
    cout << "use2<int>:    " << use2<int>()    << endl;
    cout << "use2<Test2>:  " << use2<Test2>()  << endl;
    cout << "use2<string>: " << use2<string>() << endl;
    cout << "use2<double>: " << use2<double>() << endl;
    return 0;
}

Output is (same with g++ 6.3 and clang++5.0.0 trunk):

use1<float>:  0
use1<Test1>:  10
use1<int>:    20
use1<Test2>:  30
use1<string>: 0
use1<double>: 0

use2<float>:  0
use2<Test1>:  10
use2<int>:    20
use2<Test2>:  0
use2<string>: 0
use2<double>: 0

Questions:

  1. Why use1<string> is different from use1<Test2> ? Both types are declared "at the top", both f() overloads are declared at the bottom.
  2. Why use1<Test2> is different from use1<double>? Corresponding f() overloads are on adjacent lines, is there anything special in treatment of built-in types?
  3. Why use1<Test2> is different from use2<Test2>? The type of a pointer to function in use2 seems to match the calling context in use1.
salmin
  • 457
  • 3
  • 12

1 Answers1

0

Two-phase name lookup. At the point where use1 is defined, three overloads of f are visible via normal lookup. At the point of instantiation, additional overloads may be found - but only by argument-dependent lookup. Test2 is in global namespace, so f(Test2) is found by ADL; whereas string is in namespace std, and so f(string) (which is, obviously, not in namespace std) is not found by ADL. double has no associated namespaces, and so ADL doesn't kick in at all.

In use2, f is not a dependent name, and so the second-phase lookup is not performed at all - only overloads visible at the point of definition are considered.

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
  • Thank you! Regarding use2: is there a way in use2 to obtain a function pointer that contains address of the exact same overload of f() that is resolved in use1? – salmin Aug 09 '17 at 14:33
  • I can't think of any way to "dependify" a name. The standard only envisions the second lookup phase when the name is used in a straight function call, `name(some_args)`, and not other cases where overload resolution may need to be performed. – Igor Tandetnik Aug 09 '17 at 14:36