Home > front end >  C function overload resolution from inside a function template depends on whether the function is
C function overload resolution from inside a function template depends on whether the function is

Time:02-03

The following code compiles fine:

#include <cstdio>

//namespace N {
    void f( int x ) { printf( "f( int ) called\n" ); }
    void f( double x ) { printf( "f( double ) called\n" ); }
//}

template < typename T > inline void g( const T& t ) {
//    N::f( t );
    f( t ); }

struct X {};
//namespace N {
    void f( const X& x ) { printf( "f( const X& ) called\n" ); }
//}

int main() {
    g( 1 );
    g( 1.2 );
    g( X{} );
}

But if I define f()s inside a namespace, e.g. by uncommenting the above code, then the compiler generates an error:

t.cpp:11:5: error: no matching function for call to 'f'
    N::f( t );
    ^~~~
t.cpp:25:5: note: in instantiation of function template specialization 'g<X>' requested here
    g( X{} );
    ^
t.cpp:4:10: note: candidate function not viable: no known conversion from 'const X' to 'int' for 1st argument
    void f( int x ) { printf( "f( int ) called\n" ); }
         ^
t.cpp:5:10: note: candidate function not viable: no known conversion from 'const X' to 'double' for 1st argument
    void f( double x ) { printf( "f( double ) called\n" ); }
         ^

Why is that?

CodePudding user response:

When you have N::f(t), the compiler will only find functions that are members of the namespace N and that have been declared prior to the point of use, so it won't find the overload that takes X.

When you have f(t), because the function call is unqualified, argument-dependent lookup occurs, and as a special rule, argument-dependent lookup of a dependent name within a template will also find functions in the associated namespaces that were declared before the point of instantiation (even if they were declared after the point of use). When T is the type X, which was declared in the global scope, the global namespace is an associated namespace, which means that argument-dependent lookup can find functions in the global namespace that were declared either before or after the point of use, as long as they were declared before the point of instantiation. The point of instantiation for g<X> is actually at the end of main (because the call to g<X> occurs inside main), which means that all three overloads of f can be found by argument-dependent lookup while instantiating g<X>.

This special lookup rule for argument-dependent lookup is necessary for template libraries to function. Consider for example std::sort, which is defined inside <algorithm> somewhere and (typically) included into your program near the beginning. You might need to sort an array of a user-defined type X, which has an overloaded operator<. Typically, the declaration of X and its operator< will be after the #include <algorithm> directive and therefore after the definition of std::sort. However, we still expect that when std::sort is called to sort Xs, it will be able to use the operator< for X even though the latter was declared after the former's definition.

CodePudding user response:

In the 1st case, X is declared in the global namespace, and ADL will find the void f( const X& x ) declared in the same namespace, i.e. the global namespace.

In the 2nd case, you qualified the invocation as N::f( t );, ADL won't take effect. Then void N::f( const X& x ) can't be found since it's declared after g.

If you move the definition of X into the namespace, then ADL could help you. E.g.

template < typename T > inline void g( const T& t ) {
    f( t ); 
}

namespace N {
    struct X {};
    void f( const X& x ) { printf( "f( const X& ) called\n" ); }
}

int main() {
    g( N::X{} );
}

Note that ADL doesn't work with built-in types, i.e. won't find void N::f( int x ) and void N::f( double x ) unless qualifying the invocation as N::f.

  •  Tags:  
  • Related