Search code examples
c++c++11function-pointersoverload-resolutionargument-dependent-lookup

Overload resolution, name lookup and function pointers


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.

Solution

  • 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.