Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing string literals by reference to const char* fails to compile with g++ 4.6.3

This is an example from C++ Primer, 4th edition, Chapter 16 and it's about template specialization.

#include <iostream>
#include <string>
#include <cstring>
using namespace std;

template <class T>
int compare(const T& v1, const T& v2) {
    if(v1<v2) return -1;
    if(v2<v1) return 1;
    return 0;
}

template <>
int compare<const char*>(const char* const &v1, const char* const &v2){
    return strcmp(v1,v2);
}

int main(int argc, const char *argv[])
{
    cout << compare("abc","defg") << endl;
    return 0;
}

I expect compare("abc","defg") will call the specialized version of the template. But the fact is, g++ 4.6.3 won't compile this code and give the follow error:

error: no matching function for call to 'compare(const char [4], const char [5])'

note: candidate is: template int compare(const T&, const T&)

Now given the following facts:

I. string literals, or C-style string in C++ is actually a const char array.

II. If passed as plain, non-reference types, an array will be converted to a pointer to its first element quietly.

Here I just pass string literals "abc" and "defg" as reference to const char*, and I expect they will be converted to const char* first and then passed by reference. But it seems that g++ disagree with me and refuse to compile the code.

But if I replace template specialization with function overloading, that is, replace

template <>
int compare<const char*>(const char* const &v1, const char* const &v2){
    return strcmp(v1,v2);
}

with

int compare(const char* const& v1, const char* const& v2){
    return strcmp(v1,v2);
}

then g++ will be happy to compile it.

So where on earth does the problem lie? Why I cannot pass string literals by parameter type const char* const& in the template specialization version ?

like image 591
ltux Avatar asked May 27 '13 15:05

ltux


2 Answers

The answer below is based on explanation from C++ Templates: The complete guide pp57: Using String literals as Arguments for Function templates.

template <class T>
int compare(const T& v1, const T& v2) {
    if(v1<v2) return -1;
    if(v2<v1) return 1;
    return 0;
}

This requires that both parameters v1 and v2 have the same type.

template <>
int compare<const char*>(const char* const &v1, const char* const &v2){
    return strcmp(v1,v2);
}

This requires that you have parameters with const char * type.

However, "abc" has type char const[4] whereas "defg" has type char const[5]. They are different types. Since both the specialized and templated version required reference parameters, there is no array-to-pointer decay during argument deduction. Therefore, you cannot pass different length string literals to both of them to find a match. If you provide a regular function, which does not require any argument deduction, the compiler will find a match.

If you declare non-reference parameters, you can substitute them with string literals of different length. The reason for this behavior is that during argument deduction array-to-pointer conversion (often called decay) occurs only if the paramter does not have a reference type.

like image 111
taocp Avatar answered Oct 11 '22 10:10

taocp


Template specializations do not participate in overload resolution process. Only the primary template is considered by overload resolution.

Template specializations come into play only later and only if their primary template "wins" overload resolution. I.e. template specializations are used in the process of specialization (as the name suggests), they are completely invisible during overload resolution.

For this reason, in your first example, you have only one candidate considered by overload resolution

template <class T> int compare(const T& v1, const T& v2);

In order to succeed, this candidate should pass through template argument deduction for your set of arguments. (Template argument deduction process does not care about any additional specializations either.) Template argument deduction fails in this case, since for argument of array type template parameter T is deduced as an array. And you get incompatible deductions for two arguments. The compiler gave you the error message that describes the problem. In other words, in your first example the specialized version of the template never has a chance to come into play.

In your second example, where you replaced specialization with overloading, you provided a second candidate for overload resolution. Now the compiler sees both

template <class T> int compare(const T& v1, const T& v2);
int compare(const char* const& v1, const char* const& v2);

The template candidate fails just like it did before, while the overloaded candidate succeeds.

To better illustrate the how template specializations work in this case, we can take your original code and change the primary template in order to help it to pass through overload resolution by decoupling the parameters from each other. If in your first example you change the template declaration to

template <class T1, class T2>
int compare(const T1& v1, const T2& v2) {
  ...

leaving everything else unchanged, the code will compile and it will use your specialization. But even in that case the primary template with deduced parameters will be seen as a better match to your arguments (immediate reference binding with no conversions).

like image 38
AnT Avatar answered Oct 11 '22 09:10

AnT