2

I'm getting a linker error when using a template class where I tried implementing the copy-and-swap idiom as suggested here:

What is the copy-and-swap idiom?

The template class, let's call it "TemplateClass" is partially defined like this:

template< class T >
class TemplateClass
{
    // ...
    TemplateClass< T >& operator= ( TemplateClass< T > other );
    friend void swap( TemplateClass< T >& first, TemplateClass< T >& second );
    // ...
};

I've put the implementations in a separate TemplateClass.cpp which is included in the .h file. (Edit: I have the same issue if everything is in the .h file)

The assignment operator is defined as:

template< class T >
TemplateClass< T >& TemplateClass< T >::operator= ( TemplateClass< T > other )
{
    // copy-and-swap idiom
    swap( *this, other );
    return *this;
}

and the swap method is defined as:

template< class T >
void swap( TemplateClass< T >& first, TemplateClass< T >& second )
{
    using namespace std;
    swap( first.member1, second.member1 );
    swap( first.member2, second.member2 );
    // ...
}

(Don't worry, I do not really name my members "member1" etc)

I have a similar class which is defined in the same way, but is not a template class. Everything works fine there. However, if I have a class TestClass which has a member TemplateClass< HandledClass > member and I make a call in one of its methods like

void TestClass::setMember( TemplateClass< HandledClass > newObject )
{
    member = newObject;
}

I get an unresolved external error:

LNK2019: Unresolved external symbol "void __cdecl swap( class TemplateClass &, class TemplateClass &)" (...) in function "public: class TemplateClass X & __thiscall TemplateClass X::operator=(class TemplateClass)" (...) in TestClass.obj

Or in other words: Something in TestClass calls TemplateClass<HandledClass>::operator= which does not find void swap( TemplateClass<HandledClass>, TemplateClass<HandledClass> ).

So my question is: Why does the operator not find the swap method?

It looks like it was not compiled for the template argument . Is it somehow possible to have the compiler compile friend voids as well?

I could probably ditch the friend void approach and define an in-class swap method plus an out-of-class swap method plus one in std namespace, but I don't know if it would work that way and I'd like to avoid that if possible anyway.


Solution:

this did the job:

template< class t >
class TemplateClass
{
    friend void swap( TemplateClass& first, TemplateClass& second )
    {
        // ...
    }
};

Note how i had to remove the < T > occurrences as well.

Community
  • 1
  • 1
Tim Meyer
  • 12,210
  • 8
  • 64
  • 97
  • I have my implementations in another file, yeah, but it is included in the header file, so it is basically _in_ the header file. – Tim Meyer Sep 07 '11 at 08:58
  • Well it is a bit of a wall-of-text post but I tried supplying as much information as possible ;-) – Tim Meyer Sep 07 '11 at 09:06

2 Answers2

3

This is a common problem when befriending non-member functions with templates. The friend declaration inside the TemplateClass does not befriend your swap template, but rather a non-templated free function swap that takes TemplateClass<T> for which ever T the template is instantiated (i.e. the specialization TemplateClass<int> will befriend a free function void swap( TemplateClass<int>&,TemplateClass<int>& ); that is not templated).

The best solution is to provide the swap definition inlined inside the class template definition, as that will make the compiler generate a non-templated swap function for the exact type whenever needed. As another positive side effect, that swap function will only be found during Argument Dependent Lookup, so it will not take part of overload resolution for anything that does not involve your template.

Other alternatives are befriending the whole swap template function, or befriending the particular specialization of the swap function when applied to the same T that the template has been instantiated with. The first of the options is simple in code, but it grants access to all of the specializations of the swap template, and that might have bad side effects. Befriending the particular swap specialization solves that issue, but is a bit more complex to implement (you need to forward declare the class template, then the swap template, then define the class template, and finally define the swap template).

More on this in this other answer, where the different options and syntaxes are explained with more detail.

As to the particular error message of unresolved external, that is due to how identifier lookup works. When you used swap(*this,other); inside a member function, lookup starts inside the class, and tries to find an appropriate swap. It first looks in the class context and finds the declaration of the friend free function, so lookup does not continue going outwards and adds a dependency to that particular free function. It adds the dependency and waits for the linker to locate the appropriate symbol. Because the compiler never considered the templated swap at namespace level, it never actually instantiated it, but even if it had instantiated that template, the dependency inside the operator= member function is on a free function, not that specialization.

Community
  • 1
  • 1
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Inlining itself did not do the job, but in the link you posted I realized they did not have the < T > stuff in the friend void declaration. Check the edit in my post to see the code I have chosen, if you are interested. – Tim Meyer Sep 07 '11 at 09:39
  • @Tim Meyer: If you did not misspelled anything, just inlining the function definition inside the class template definition should have worked with or without the `` (if `T` is the actual template argument). Inside the class template scope, the name of the class template is a short hand for the specialization of the template that is being instantiated, which means that `TemplateClass` and `TemplateClass` are exact synonyms if the template argument is `T`. – David Rodríguez - dribeas Sep 07 '11 at 14:04
  • I have precompiled headers active (though the header file only excludes windows/third party libs), maybe there was an issue there. I did do a full rebuild at some point after removing the < T > – Tim Meyer Sep 07 '11 at 15:48
0

You should either put the declaration of class template into header file, or if you know in advance all types that this class template will be instantiated with, provide explicit instantiation at header file:

template< class T >
class TemplateClass
{
    // ...
    TemplateClass< T >& operator= ( TemplateClass< T > other );
    friend void swap( TemplateClass< T >& first, TemplateClass< T >& second );
    // ...
};

template class TemplateClass<FirstType>;
template class TemplateClass<SecondType>;
// ...

// and the same for swap function
template void swap<FirstType>( TemplateClass<FirstType>& first, TemplateClass<FirstType>& second );
template void swap<SecondType>( TemplateClass<SecondType>& first, TemplateClass<SecondType>& second );

It's tedious but sometimes it's the best option.

Regarding why your swap doesn't link: you declare friendnes with non-template function swap, which doesn't exist.Try this:

template< class T >
class TemplateClass
{
    // ...
    TemplateClass< T >& operator= ( TemplateClass< T > other );
    template < class U > friend void swap( TemplateClass< U >& first, TemplateClass< U >& second );
    // ...
};

Additional efforts required if you wanna be a purist and only be friends with 'your' swap (swap with the same template parameters).

Alexander Poluektov
  • 7,844
  • 1
  • 28
  • 32
  • I have thought about this, but I want to use it as a generic class in different projects so it's not an option for me unfortunately. Any clue why the compiler does not compile the friend void, even if the declaration is in the header file? – Tim Meyer Sep 07 '11 at 09:05