Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to specialize template function to let it accept passing a char array as argument?

Tags:

c++

templates

// problem.cpp:
#include <string>

template<typename T> void func(const T & v);

int main() {
        int i;
        float f;
        char * cp;
        char ca[4];

        func(i);
        func(f);
        func(cp);
        func(std::string("std::string"));
        func(ca);
        func("string_literal");

        return 0;
}

// problem2.cpp
#include <string>

template<typename T> void func(const T & v);

// undefined reference to `void func<int>(int const&)'
template<> void func<int>(const int & v) { }

// undefined reference to `void func<float>(float const&)'
template<> void func<float>(const float & v) { }

// undefined reference to `void func<char*>(char* const&)'
template<> void func<char *>(char * const & v) { }

// void func<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
template<> void func<std::string>(std::string const & v) { }

// undefined reference to `void func<char [4]>(char const (&) [4])'
// ???

// undefined reference to `void func<char [15]>(char const (&) [15])'
// ???

Found two solutions:

a) in problem2.cpp:

template<> void func<char[4]>(const char (&v)[4]) { }
template<> void func<char[15]>(const char (&v)[15]) { }

b) in problem.cpp:

template<typename T, unsigned N> void func(const T (&v)[N]) { func(v+0); }
and then in problem2.cpp, add the newly missing
template<> void func<const char *>(const char * const & v) { }

Sorry akappa, had to edit again to clarify that they are two independant solutions ...

akappa: My only way to add something to this question is by editing it. Neither can I comment nor can I add an answer. May have something to do with »Stack Overflow requires external JavaScript from another domain, which is blocked or failed to load.« which I don't know how to resolve because I don't know that exactly SO is trying to tell me there.

like image 787
user1468595 Avatar asked Aug 01 '12 11:08

user1468595


1 Answers

Function specializations are tricky in that they must match the type of the argument exactly. In the case of an array (a string literal is also an array), the compiler will perform type deduction and find out what the exact type is, and it will then look for that particular symbol in the program.

In particular, the type of ca is char[4], so when calling the template, the deduced type is T == char[4] and the function signature that it expects to find is void func<>( const char (&)[4] ) As of your two solutions, they are completely different approaches. In the first case, you are specializing the template for the specific types that are used. This will become painful, as with each new string literal size or type you use, you will need to manually add an specialization. This is incidentally the reason why templates should (more often than not) be defined in the header, so that you don't need to name all possible template arguments in explicit instantiations(*)...

The second solution is completely different. In this case you are creating a second unrelated (to some extent) base template. That base template is obtaining a pointer to the first element of the array and calling the original template with that, effectively changing the types (and loosing information in the process: the size is now lost). At this point, all calls with arrays will match that second template and be forwarded as calls to the original template with a pointer, reducing the need to specialize on the size of the array (the compiler resolves those specializations).

Also note that if you only want to allow passing char arrays, the dispatcher template need not accept all types, it could have a single non-type argument:

template <std::size_t N>
void f( const char (&a)[N] ) { f( &a[0] ); }

Summing up:

Avoid template specializations of functions, as these are cumbersome to deal with. The type of an array includes the size, which in turn means that you need to specialize for each and every potential array size. Alternatively you can add a secondary template that will dispatch to your original template, performing the conversion to a pointer.


* Note that if the implementation of each and all specializations is the same, you can avoid specializing (and copying code), while maintaining the same behavior by providing the definition of the template in the .cpp and then manually instantiate the template:

template <typename T> void func( T const & ) {
   // code goes here
}
template void func<int>( int const & );
template void func<double>( double const & );
//...
like image 82
David Rodríguez - dribeas Avatar answered Sep 27 '22 18:09

David Rodríguez - dribeas