Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ enforce second-pass name lookup in template function

Is there some way to force C++ compilers to perform name lookup for a given symbol during template instantiation (and not before)?

Given the following code:

template <class T>
auto wrapper( T t ) -> decltype( f( t ) )
{
    return f( t );
}

unsigned char f( int x ) { return x % 256; }
unsigned char f( unsigned char x ) { return x; }

int main( int, char ** )
{
    auto x = wrapper( 3100 );
    return 0;
}

Is there anything I can do (apart from moving the definition of f to the top) in order to make that code compile and give the same results as if all definitions of f were available before the definition of wrapper?

I could not find anything, probably because I do not know how to phrase this question properly. All argument types of f can be assumed to be user-defined types, if this helps.

like image 303
Markus Mayr Avatar asked Jan 28 '15 07:01

Markus Mayr


2 Answers

Is there some way to force C++ compilers to perform name lookup for a given symbol during template instantiation (and not before)?

Yes. First of all, the name must be dependent. The name f in wrapper when used as f(t) is dependent because t is type-dependent. [temp.dep]/1:

In an expression of the form:

        postfix-expression ( expression-list opt)

where the postfix-expression is an unqualified-id, the unqualified-id denotes a dependent name if

  • any of the expressions in the expression-list is a pack expansion (14.5.3),
  • any of the expressions in the expression-list is a type-dependent expression (14.6.2.2), or
  • if the unqualified-id is a template-id in which any of the template arguments depends on a template parameter.

The problem is that names declared after the template itself, i.e. only in the instantiation but not the definition context, can solely be found using argument dependent name lookup. Your f overloads only take fundamental types, but those do not have the global namespace associated with them according to [basic.lookup.argdep]/2:

If T is a fundamental type, its associated sets of namespaces and classes are both empty.

Thus the fs you declared can never be found if the arguments are of the same type as the parameters. A little trick can help:

template <typename T>
struct refwrap
{
    T&& t;
    refwrap(T&& t) : t(std::forward<T>(t)) {}
    operator T&&() {return std::forward<T>(t);}
};

template <typename T>
auto make_refwrap( T&& t ) -> refwrap<T> // making use of reference collapsing
{ return {std::forward<T>(t)}; }         // inside refwrap to get forwarding

This template, when declared in the global namespace, will cause ADL to consider it. Rewrite wrapper as follows:

template <class T>
auto wrapper( T t ) -> decltype( f( make_refwrap(t) ) )
{
    return f( make_refwrap(t) );
}

Demo. This is not the proper way to do it though, as it will fail in more complex scenarios.

like image 135
Columbo Avatar answered Oct 24 '22 01:10

Columbo


This would work with template specialization. Note that you have to decide what the default function is, because i cannot see it in question.

// default function
template <class T>
unsigned char f( T x ) { return x; }

// specialization for int
template <>
unsigned char f( int x ) { return x % 256; }

int main( int, char ** )
{
    auto x = f( 3100 );
    return 0;
}
like image 1
Chiel Avatar answered Oct 24 '22 02:10

Chiel