Search code examples
c++templatesc++11type-deductionstdstring

basic_string<CharT> vs. CharT*


This is a FAQ, yet I could not find a satisfying answer. In my project we support std::string and now have to also support wide strings. So we want to move to basic_string, but then, things stop working nicely and parameters need to be spelled out explicitly:

#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, it fails for the well-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 get it why does it work for bar? Why doesn't the explicit instantiation of foo for char (either with explicit template parameters or with deduction) suffice to work around this problem?

It seems that it means that instead of using templates and basic_string in the exposed 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.

Thanks!


Solution

  • For bar("abc") there is an implicit conversion from char const[4] to std::string. foo differs from bar in that it's not actually a function but a function template. Its template arguments need to be known in order to build the correct function.

    The first call to foo explicitly provides the template arguments, so it builds a function that looks like this:

    void foo(const std::basic_string<char, std::char_traits<char>, std::allocator<char>>&);
    

    The implicit conversion kicks in and everything is fine.

    The third call does not provide the template arguments, so the compiler has to figure out the type of CharT, Traits and Allocator from the type char const[4]. This type doesn't carry that information with it, so deduction fails and overload resolution cannot find the correct function.