【问题标题】:basic_string<CharT> vs. CharT*basic_string<CharT> 与 CharT*
【发布时间】:2014-12-22 20:33:51
【问题描述】:

这是一个常见问题解答,但我找不到令人满意的答案。在我的项目中,我们支持std::string,现在还必须支持宽字符串。所以我们想转移到basic_string,但随后,事情就停止了,需要明确说明参数:

#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");
}

好的,众所周知的原因它失败了:

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>&)
^

我不明白为什么它适用于bar?为什么 foochar 的显式实例化(使用显式模板参数或使用推导)不足以解决此问题?

这似乎意味着我们将不得不将其用作实现细节,而不是在公开的 API 中使用模板和 basic_string,但向用户公开 std::stringstd::wstring 等的重载。真可惜。

谢谢!

【问题讨论】:

  • foo的显式版本与clang3.5一起工作,只有带参数推导失败(最后一行),因为它不能推导参数。

标签: c++ templates c++11 type-deduction stdstring


【解决方案1】:

对于bar("abc"),存在从char const[4]std::string 的隐式转换。 foobar 的不同之处在于它实际上不是一个函数,而是一个函数模板。为了构建正确的函数,需要知道它的模板参数。

foo 的第一次调用显式提供了模板参数,因此它构建了一个如下所示的函数:

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

隐式转换启动,一切正常。

第三次调用没有提供模板参数,因此编译器必须从char const[4] 类型中找出CharTTraitsAllocator 的类型。此类型不携带该信息,因此推导失败并且重载解析找不到正确的函数。

【讨论】:

  • 我想我真的很沮丧重载函数和显式实例化的函数模板的行为不同。谢谢!
【解决方案2】:

这对你有用吗:

template <typename StringT>
void foo(const StringT& the_string)
{
    typedef decltype(the_string[0]) CharT;
    // do the work
}

这可以将StringT 推断为std::stringstd::wstringconst char[N]const wchar_t[N]std::vector&lt;char&gt; 等。如果您希望事先将 C 样式字符串隐式转换为 std::string,那么您可以使用所有 STL 集合共有的成员函数,添加捕获数组的转发重载:

template <typename CharT, size_t N>
void foo(const CharT (&char_array_or_literal)[N])
{
    foo(std::basic_string<CharT>(char_array_or_literal));
}

也许另一个字符指针:

template <typename CharT>
void foo(const CharT* char_ptr)
{
    foo(std::basic_string<CharT>(char_ptr));
}

另一方面,如果您需要basic_string的所有功能,则应使用广泛模板进行转发:

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]));
}

【讨论】:

  • 我认为这个答案最清楚地说明了正在发生的事情 - 问题中的代码模板化了错误的东西(basic_string 的详细信息)而不是字符串的概念。使用此答案中的解决方案,甚至可以传入 std::array 和 std::vector 。稍微偏离主题,考虑仅使用以下约定坚持 std::string它是 UTF-8 编码的,并且只转换为需要它的 API 的宽字符串。
  • 我担心使用诸如StringT 之类的“无约束”参数:可能涉及到太多其他事情。但确实,通过一些检查StringT 是什么,它可能真的很好。谢谢!
【解决方案3】:

最好的解决方法似乎是为 std::stringstd::wstring 提供非模板重载,以委托给函数模板 (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); }

【讨论】:

  • “最好的解决方法似乎是” 为什么不使用std::wstring 文字后缀?
  • @dyp:当参数是非文字的 C 风格字符串时怎么办?
  • @BenVoigt 啊,好点子。我只考虑了 OP 的测试用例。
  • 是的,我同意,重载效果很好,这就是我在问题末尾的意思。但我是想避免这种情况。
猜你喜欢
  • 2017-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多