Basic_string <CharT> vs CharT *
This is a FAQ, but I couldn't find a satisfactory answer. In my project we support std::string
and should now also support wide strings. Therefore, we want to switch to basic_string
, but then everything stops working nicely, and the parameters should be clearly spelled out:
#include <string>
template <typename CharT, typename Traits, typename Allocator>
void
foo(const std::basic_string<CharT, Traits, Allocator>&)
{}
template void foo(const std::string&);
// template void
// foo<char, std::char_traits<char>, std::allocator<char>>(const std::string&);
void bar(const std::string& s)
{}
int main()
{
bar("abc");
foo<char, std::char_traits<char>, std::allocator<char>>("def");
foo("def");
}
OK, this is happening for a known reason:
clang++-mp-3.5 -Wall -std=c++11 foo.cc
foo.cc:20:3: error: no matching function for call to 'foo'
foo("def");
^~~
foo.cc:5:1: note: candidate template ignored: could not match
'basic_string<type-parameter-0-0, type-parameter-0-1, type-parameter-0-2>'
against 'char const[4]'
foo(const std::basic_string<CharT, Traits, Allocator>&)
^
What I don't understand is why it works for bar
? Why is explicit instantiation foo
for char
(either with explicit template parameters or subtraction) not enough for this problem ?
This seems to mean that instead of using templates and basic_string
in the public API, we will have to use it as an implementation detail, but expose the user with overloads for std::string
, std::wstring
etc. which is a shame.
Thank!
source to share
For bar("abc")
there is an implicit conversion from char const[4]
to std::string
. foo
different from the bar
fact that it is not actually a function, but a function template. Its template arguments must be known in order to build a correct function.
The first call foo
explicitly provides template arguments, so it creates a function that looks like this:
void foo(const std::basic_string<char, std::char_traits<char>, std::allocator<char>>&);
Implicit conversion and everything is fine.
The third call does not provide template arguments, so the compiler must determine the type CharT
, Traits
and Allocator
from the type char const[4]
. This type does not carry this information, so no inference is performed and overload resolution cannot find the correct function.
source to share
This works for you:
template <typename StringT>
void foo(const StringT& the_string)
{
typedef decltype(the_string[0]) CharT;
// do the work
}
It can output StringT
as the std::string
, std::wstring
, const char[N]
, const wchar_t[N]
, std::vector<char>
, etc. And if you want implicit conversions of C-style strings to std :: string beforehand, so that you can use member functions common to all STL collections, add an overloading overload that captures arrays:
template <typename CharT, size_t N>
void foo(const CharT (&char_array_or_literal)[N])
{
foo(std::basic_string<CharT>(char_array_or_literal));
}
And maybe another one for character pointers:
template <typename CharT>
void foo(const CharT* char_ptr)
{
foo(std::basic_string<CharT>(char_ptr));
}
If, on the other hand, you want all the functionality basic_string
, then the broad pattern should be used for forwarding:
template <typename CharT, typename Traits, typename Allocator>
void foo(const std::basic_string<CharT, Traits, Allocator>& the_string)
{
// the real work is done here
}
template <typename StringLikeT>
void foo(const StringLikeT& the_string_like_thing)
{
typedef decltype(the_string_like_thing[0]) CharT;
// this turns string literals, arrays, pointers, vectors, std::array, all into basic_string
foo(basic_string<CharT>(&the_string_like_thing[0]));
}
source to share
The best workaround is to provide non-templated overloads for std::string
and std::wstring
that pass a template to a function ( Demo at Coliru ):
template <typename CharT, typename Traits, typename Allocator>
void foo(const std::basic_string<CharT, Traits, Allocator>&)
{}
inline void foo(const std::string& u) { foo<>(u); }
inline void foo(const std::wstring& u) { foo<>(u); }
source to share