views:

338

answers:

2

I'm trying to create a function:

template <typename T>
void doIt( T*& p )
{
   if ( !p ) { return; }
   T& ref = *p;
   getClassName( ref );
}

where the behavior varies according to the type of p passed in. In particular, the version of getClassName called should depend upon the type of p. In the following example, I can successfully call:

doIt<myClass1>( myClass1*& )
doIt<myClass1<int> >( myClass1*& )
doIt<myClass2>( myClass2*& )
doIt<myClass2<int> >( myClass2*& )

but it fails when I call:

doIt< std::vector<int, std::allocator<int> > >( std::vector<int, std::allocator<int>>*& )

with the error:

a.cxx: In function ‘void doIt(T*&) [with T = std::vector<int, std::allocator<int> >]’:
ba.cxx:87:   instantiated from here
a.cxx:33: error: invalid initialization of reference of type ‘MyClass1&’ from expression of type ‘std::vector<int, std::allocator<int> >’
a.cxx:16: error: in passing argument 1 of ‘const char* getClassName(MyClass1&)’

(gcc 4.2.4).

If I move the declaration of:

template<typename T, typename A>
char const* getClassName( std::vector<T,A>& ) { printf("std::vector<T,A>\n"); return NULL; }

before doIt -- then it compiles. So,

  • Why is it required that getClassName( std::vector<T,A>& ) appears before doIt but not getClassName( MyClass2T<T>& )
  • What can I do to make doIt independent of std::vector? (I want to be able to place doIt in its own header and not have to know about std::vector, or any of the specializations, which will be user-defined).

.

#include <stdio.h>
#include <assert.h>
#include <vector>

//template<typename T>
//char const* getClassName( T& );

//template<typename T, typename A>
////char const* getClassName( std::vector<T,A>& ) { printf("std::vector<T,A>\n"); return NULL; }

#if 1
// ---------  MyClass2
struct MyClass1
{};

char const* getClassName( MyClass1& ) { printf("MyClass1\n"); return NULL; }

// ---------  MyClass1T
template< typename T>
struct MyClass1T
{};

template<typename T>
char const* getClassName( MyClass1T<T>& ) { printf("MyClass1T<T>\n"); return NULL; }
#endif


template <typename T>
void doIt( T*& p )
{
   if ( !p ) { return; }
   T& ref = *p;
   getClassName( ref );
}


// ---------  MyClass2
struct MyClass2
{};


// declared after doIt, OK.
char const* getClassName( MyClass2& ) { printf("MyClass2\n"); return NULL; }

// ---------  MyClass2T
template< typename T>
struct MyClass2T
{};

// declared after doIt, OK.
template<typename T>
char const* getClassName( MyClass2T<T>& ) { printf("MyClass2T<T>\n"); return NULL; }

template<typename T, typename A>
char const* getClassName( std::vector<T,A>& ) { printf("std::vector<T,A>\n"); return NULL; }



void test()
{
#if 1
   MyClass1 mc1;
   MyClass1* mc1p = &mc1;
   doIt( mc1p );

   MyClass2 mc2;
   MyClass2* mc2p = &mc2;
   doIt( mc2p );

   MyClass1T<int> mc1t;
   MyClass1T<int>* mc1tp = &mc1t;
   doIt( mc1tp );

   MyClass2T<int> mc2t;
   MyClass2T<int>* mc2tp = &mc2t;
   doIt( mc2tp );

   // Nested templates are OK.
   MyClass2T<MyClass1> mc2t2;
   MyClass2T<MyClass1>* mc2tp2 = &mc2t2;
   doIt( mc2tp2 );
#endif

#if 1
   std::vector<int> v;
   std::vector<int>* vp = &v;
   doIt( vp );                   // FAIL!
#endif
}
+3  A: 

Why is it required that getClassName( std::vector& ) appear before doIt but not getClassName( MyClass2T& )

A declaration in scope is required for any function. When you instantiate your template function with a vector<int> it expects a function with the signature getClassName(vector<int>&) to be present (at least a prototype) for compilation to succeed.

What can I do do make doIt independent of std::vector? (I want to be able to place doIt in its own header and not have to know about std::vector, or any of the specializations, which will be user-defined)

Read the FAQ on Templates. Try putting the prototype of all of doIt's dependent template functions before first instantiation of doIt.

dirkgently
So why is a forward declaration required for char const* getClassName( std::vector<T,A>
Q1. Ans: vector<T> is an incomplete type. Q2. Ans: This is explained in FAQ35.14.
dirkgently
Makes sense. Thanks.
This has not to do with `vector<T>` being incomplete (it's a complete type, actually, if you include the vector header `<vector>`). The error is because of name-lookup. See below.
Johannes Schaub - litb
+2  A: 

The reason for the failure is that at instantiation, no unqualified name lookup for the functions occur (but only ADL - Argument Dependent Lookup). The instantiation context is (taken from 14.6.4.1/6 of the C++ Standard):

The instantiation context of an expression that depends on the template arguments is the set of declarations with external linkage declared prior to the point of instantiation of the template specialization in the same translation unit.

The point of instantiation of all those template specializations you called in this case is just after the definition of test (read 14.6.4.1/1). So, all functions you declared are visible in your test function using unqualified lookup, but lookup for them is actually different for the function calls:

A function call that depends on a template parameter within a template is looked up like this:

  • Names from the template definition context are considered by both ordinary lookup and ADL.
  • Names from instantiation context are considered only for ADL.

This means that because there is no suitable getClassName function declared in the definition context of the template, a suitable function has to be found in the instantiation context using ADL - otherwise the call will fail and not find any declaration.

Argument Dependent Lookup (ADL)

For an argument of type std::vector<T>, ADL searches for functions in namespace std and the namespace of T. Putting the getClassName function into the std namespace would work for this (but doing so is not allowed by the Standard because that yields to undefined behavior - this should be done only as the last resort).

To see the effects of ADL try to call doIt with a vector of MyClass2 instead of int. Since then T = MyClass2, ADL will search in the namespace of MyClass2 for a suitable function accepting a std::vector<MyClass2> and will succeed - opposed to when you use int, which will only look into std.

For the other function calls, their respective declarations are all found too, because they are all declared in the global namespace, in which the argument types of the function calls are defined too (MyClass1, MyClass2 etc).

The C++ FAQ is good, but it doesn't go deep into templates (haven't found any mentioning of ADL in it). There is a dedicated template faq that handles some of the more intricate pitfalls.


Beware of undefined behavior

Note that many compilers will accept the code even when you put that declaration i showed after the test function (instead of before it). But as the above Standard quote says, then the declaration won't be part of the instantiation context and the rule found in 14.6.4.2/1 is to be watched:

If the call would be ill-formed or would find a better match had the lookup within the associated namespaces considered all the function declarations with external linkage introduced in those namespaces in all translation units, not just considering those declarations found in the template definition and template instantiation contexts, then the program has undefined behavior.

Thus, what would appear to work would be undefined behavior. It's valid for a compiler to accept it, but it's likewise valid for one to reject it or to crash and terminate. So watch that any name needed is indeed visible at the instantiation context as explained.

Hope this helps.

Johannes Schaub - litb