ScaryAardvark
ScaryAardvark

Reputation: 2965

strange c++ template method specialisation issue

I've come across a strange problem with method specialisation.

Given this code...

#include <string>

class X
{
public:

    template< typename T >
    void set( T v );
};

template<>
void X::set( const std::string & v )
{
}

template<>
void X::set( int v )
{
}

int main( int, char *[] )
{
    X x;

    std::string y;

    x.set( y );

    x.set( 1 );
}

When I link it with g++ 4.3.3 I get an undefined reference to void X::set<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >).

Which is basically an undefined reference to void X::set( std::string ).

So my question is, why doesn't the compiler use the specialisation with const std::string & ?

If I explicitly call x.set< const std::string & >( y ) then this compiles and links fine.

Upvotes: 4

Views: 217

Answers (4)

Ise Wisteria
Ise Wisteria

Reputation: 11669

Probably this article will explain the situation.
You might expect the specialization void X::set( const std::string& ) will participate in overload resolution. However, surprisingly, specializations don't participate in overload resolution.
In the call x.set( y ), the compiler deduces the type of T in the primary template from the argument y with a type std::string. So, the compiler deduces that T is std::string, then searches matching specialization.
However, since std::string and const std::string& are different types, the compiler selects primary template in the end.

After proper primary template is selected, the matching specialization is selected in the same manner as the case for class template. The reason that the specialization set( const std::string& ) isn't selected is similar to that the specialization A< std::string > isn't selected in the following code:

template< class > class A; // primary

template<> class A< const std::string& > {}; // specialization

int main() {
  A< std::string > a; // This doesn't select the specialization
}

Upvotes: 2

Adrian Regan
Adrian Regan

Reputation: 2250

Reverting back to original proposition after compilation:

Your method signature for string should not be a reference. Should be:

template<>
void X::set( const std::string v )
{
}

This is because in your template definition you have specified T paramater and not T& paramater

Upvotes: 3

Jan Hudec
Jan Hudec

Reputation: 76386

It's altogether incorrect syntax. When specializing templates, you have to include the angle brackets with the types you are specializing for. E.g.:

template<typename T>
struct X { ... };

template<>
struct X<int> { ... };
//      ^^^^^ see, there have to be angle brackets after the identifier

So

template<>
void X::set(std::string &) { ... }

is not specializing anything, it is implementing

class X {
    template<>
    void set(std::string &) { ... }
};

which is altogether different function. What I don't understand is why gcc didn't produce an error because the class does not have that member.

Now even if you used the supposedly-correct syntax, it wouldn't be correct, because, as already answered by Tom, you can't specialize functions (just overload with non-template version). In C++03, that is; it is allowed in C++0x.

Upvotes: 3

Tom
Tom

Reputation: 5309

What you want to use is overloading.

class X
{
public:
    void set( int v); //the case for ints
    void set( const std::string&) //the case for strings

    //The default catch all case.
    template< typename T >
    void set( T v );
};

//not a template specialisation.
void X::set( int v )
{
}
//not a template specialisation.
void X::set( const std::string & v )
{
}
//the catch all case 
template<typename T>
void X::set(T v)
{
}

The non-template operators will be chosen ahead of the template because they provide a better match if available. Otherwise the template will be chosen

Upvotes: 2

Related Questions