Search code examples
c++templatesresolution

Template Resolution in <Inside The C++ Object Model>


In Chapter 7 of "Inside the C++ Object Model," it was written that the resolution of a nomember name depends on whether the use of name is related to "the type of parameter used to instantiate the template." I write a test:

/// -------------Test.h---------------
#ifndef TEST_H
#define TEST_H

#include <iostream>

using namespace std;

extern double foo(double t);

template <typename T>
class Test {
    public:
        void fun1() {
            member = foo(val);
        }
        T fun2() {
            return foo(member);
        }
    private:
        int val;
        T member;
};

#endif

and

/// -------------test1.cc-------------
#include <iostream>

using namespace std;

double foo(double t) {
    cout << "foo doule is called" << endl;
    return t;
}

int foo(int t) {
    cout << "foo int is called" << endl;
    return t;
}
-------------test.cc--------------
#include "Test.h"

extern int foo(int t);

int main() {
    Test<int> fi;
    fi.fun1();
    fi.fun2();
    return 0;
}

I expect "foo double is called\n foo int is called", but I got "foo double is called\n foo double is called". My g++ version is bellow. I'll be appreciate it if you can help me.

My g++ version


Solution

  • I'm afraid the book is not painting the complete picture (and it's a bit aged). Yes, foo(member) is a function call that is dependent on the template parameter. But the specific way in which functions are looked up in templates is described in the C++ standard at [temp.dep.candidate]:

    For a function call where the postfix-expression is a dependent name, the candidate functions are found using the usual lookup rules ([basic.lookup.unqual], [basic.lookup.argdep]) except that:

    • For the part of the lookup using unqualified name lookup, only function declarations from the template definition context are found.
    • For the part of the lookup using associated namespaces ([basic.lookup.argdep]), only function declarations found in either the template definition context or the template instantiation context are found.

    foo's overloads can be looked up in one of two ways. By direct unqualified lookup, and by argument dependent lookup (aka ADL). Simple unqualified lookup considers only the names that are known at the point of the template definition. Since you only declared foo(double), that is the only overload found at the point of the template definition.

    At the point of instantiation, the compiler will try to do ADL to find more foo's, but fundamental types don't contribute to ADL. int cannot be used to find foo(int). So the compiler can do only one thing, convert the integer to a double, and call foo(double).

    If you want to test your compilers ADL, you just need to add a simple user defined type and overload. For instance:

    enum E{};
    E foo(E) {
      cout << "foo E is called\n";
      return {};
    }
    
    int main() {
        Test<E> fi;
        fi.fun1();
        fi.fun2(); 
        return 0;
    }