Help with a template specialization

2019-06-27 23:16发布

I'm working with/learning template function specialization rules. I start with this function

template<typename T>
std::string toString(const T& t)
{
    ostringstream out;
    out << t;
    return out.str();
}

Now I'd like to specialize it for const char*

typedef const char* ccharPtr;

template<>
std::string toString(const ccharPtr& s)
{
    cout << "in specialization" << endl; // just to let me know
    return std::string(s);
}

I'd like to do that without a typedef, but so far I can't figure it out.

That specialization works for a const char*, but not for a char*.

const char* s1 = "Hi"
cout << toString(s1); // works
char s2[] = "There";
cout << toString(s2); // doesn't work, since s2 isn't const char*
cout << toString(", Bob"); // doesn't work. Why not?

I'd like a single specialization to work for each case, but having trouble figuring it out.

标签: c++ templates
4条回答
【Aperson】
2楼-- · 2019-06-27 23:28

Why specialise? Just overload the function. Fully specialising function templates is usually not required.

template <typename T>
std::string toString(const T& in)
{
    ostringstream out;
    out << in;
    return out.str();
}

std::string toString(char const* in)
{
    return in;
}

OK, if you really want to do this, then you have to consider that the type of string literals — although they implicitly convert to char const* — is char const[N].

template <typename T>
std::string toString(T const & t) {
    ostringstream out;
    out << t;
    return out.str();
}

template <>
std::string toString(char const* const & s) {
    cout << "(S1)";
    return std::string(s);
}

template <size_t N>
std::string toString(char (&s)[N]) {
    cout << "(S2)";
    return std::string(s);
}

template <size_t N>
std::string toString(char const (&s)[N]) {
    cout << "(S3)";
    return std::string(s);
}

int main() {
    const char* s1 = "Hi";
    cout << toString(s1) << endl;
    char s2[] = "There";
    cout << toString(s2) << endl;
    cout << toString(", Bob") << endl;
}

// Output:
// (S1)Hi
// (S2)There
// (S3), Bob

Live demo.

You can omit specialisation S2, and then both "There" and ", Bob" will use S3.

Be warned that, actually, this isn't specialisation at all. I've rather cheated by creating new function templates. But I had to to get the size_t parameter in; you could only do real specialisation here if you picked one value for N and wrote it into the function signature as part of a concrete type, or if you could partially specialise function templates.

查看更多
等我变得足够好
3楼-- · 2019-06-27 23:29

I think this should work:

template <typename T>
    std::string to_string(const T &)
{
    ...
}

// non templated
std::string to_string(const char* x)
{
    ...
}
查看更多
萌系小妹纸
4楼-- · 2019-06-27 23:33

The reason it doesn't work is because it's not actually the right type. String literals are not of type const char*, they are of type const char[N]. When you pass a string literal, then T is deduced to be char[N]. This is an exact match and the specialization is not. You cannot speciailize for string literals, as that would require a partial spec and C++ does not support partial function specializations.

查看更多
beautiful°
5楼-- · 2019-06-27 23:33

I had a similar problem with Sun Studio C++ compiler.

At first, I used two array overloads (char (&)[N] with and without const) and two pointer overloads (char* and const char*).

To my surprise for calls like toString("quoted literal") the const char* was used and it seemed impossible to force the compiler to choose the array overload. What is even more interesting - for arguments like char lit[] = "literal", toString(lit) chose the array overload!

After some thought, I found a way to have a code which behaves as partial function specializations. The trick is to make use of SFINAE

First, define a template to distinguish (const) char* from (const) char[N]

template<typename T>
    struct is_literal {
};

template<size_t N>
struct is_literal<const char[N]> {
    typedef std::string type;
    static const size_t len = N - 1;  // subtract terminating null char from N
};

template<size_t N>
struct is_literal<char[N]> {
    typedef std::string type;
    static const size_t len = N - 1;
};

template<>
struct is_literal<const char*> {
    typedef std::string ptr_type;
};

template<>
struct is_literal<char*> {
    typedef std::string ptr_type;
};

Inner typedef type acts like enable_if and ptr_type like disable_if

We can use is_literal to implement the functions:

template<typename T>
typename is_literal<T>::type
toString(T& arg)
{
    std::cout << "(literal)";
    // note the second argument which means length
    // this can be a performance gain because no strlen call is needed
    return std::string(arg, is_literal<T>::len); 
}

template<typename T>
typename is_literal<T>::ptr_type
toString(T& arg)
{
    std::cout << "(raw pointer)";
    return std::string(arg);
}

Some compilers may have problems with const - for those const T& arg overloads might be needed.

I use this solution mainly for performance reasons when I have frequently called functions and want to save strlen calls or replace strcpy with simple memcpy

查看更多
登录 后发表回答