【问题标题】:std::transform() and toupper(), no matching functionstd::transform() 和 toupper(),没有匹配的函数
【发布时间】:2011-10-31 04:54:44
【问题描述】:

我尝试了这个问题的代码C++ std::transform() and toupper() ..why does this fail?

#include <iostream>
#include <algorithm>

int main() {
  std::string s="hello";
  std::string out;
  std::transform(s.begin(), s.end(), std::back_inserter(out), std::toupper);
  std::cout << "hello in upper case: " << out << std::endl;
}

理论上它应该可以工作,因为它是 Josuttis 书中的示例之一,但它不能编译 http://ideone.com/aYnfv

为什么 GCC 会抱怨:

no matching function for call to ‘transform(
    __gnu_cxx::__normal_iterator<char*, std::basic_string
        <char, std::char_traits<char>, std::allocator<char> > >, 
    __gnu_cxx::__normal_iterator<char*, std::basic_string
        <char, std::char_traits<char>, std::allocator<char> > >, 
    std::back_insert_iterator<std::basic_string
        <char, std::char_traits<char>, std::allocator<char> > >,
    <unresolved overloaded function type>)’

我在这里遗漏了什么吗?是 GCC 相关的问题吗?

【问题讨论】:

  • std命名空间中的那个与函数原型不匹配。所以它会抱怨。
  • @CodeMonkey:不止一个。所以它会抱怨。
  • @Tomalak:是的,同样的区别。

标签: c++ algorithm stl


【解决方案1】:

只需使用::toupper 而不是std::toupper。也就是说,toupper 定义在全局命名空间中,而不是定义在 std 命名空间中。

std::transform(s.begin(), s.end(), std::back_inserter(out), ::toupper);

它的工作:http://ideone.com/XURh7

您的代码无法正常工作的原因:命名空间std 中还有另一个重载函数std 导致解析名称时出现问题,因为编译器无法确定您指的是哪个重载,当您只需通过std::toupper。这就是编译器在错误消息中显示unresolved overloaded function type 的原因,这表明存在重载。

因此,为了帮助编译器解决正确的重载问题,您必须将 std::toupper 强制转换为

(int (*)(int))std::toupper

也就是说,以下是可行的:

//see the last argument, how it is casted to appropriate type
std::transform(s.begin(), s.end(), std::back_inserter(out),(int (*)(int))std::toupper);

请自行查看:http://ideone.com/8A6iV

【讨论】:

  • 这确实有效,但为什么呢? toupper 也在 std 命名空间中(通过 &lt;cctype&gt;)。
  • @Tomalak: 这就是我所说的"..overloaded function toupper in the namespace std" 这意味着std::toupper 有过载。
  • @Oli: &lt;locale&gt; 定义 template &lt;class charT&gt; charT toupper(charT c, const locale&amp; loc)
  • 如果您使用::toupper,最好包含C 标头(&lt;ctype.h&gt;)以确保它位于全局命名空间中。然后,您将依赖已弃用但明确指定的行为,而不是未指定的行为。
  • 对不起,但这个答案的开头(“只需使用 toupper 而不是 std::toupper”)是不完整的。如果 OP 使用 using namespace stdit ceases to be a viable workaround(更不用说您正在使用已弃用或未指定的行为,即使它确实有效)。
【解决方案2】:

问题

std::transform(
    s.begin(),
    s.end(),
    std::back_inserter(out),
    std::toupper
);

没有匹配函数调用‘transform(__gnu_cxx::__normal_iterator&lt;char*, std::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt; &gt;, __gnu_cxx::__normal_iterator&lt;char*, std::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt; &gt;, std::back_insert_iterator&lt;std::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt; &gt;, &lt;unresolved overloaded function type&gt;)

这是一个误导性错误;有趣的部分不是调用没有“匹配函数”,而是为什么没有匹配函数。

为什么是您将“&lt;unresolved overloaded function type&gt;”的函数引用作为参数传递,而 GCC 更愿意在调用时出错,而不是在重载解析失败时出错。 p>


说明

首先,您应该考虑如何在 C++ 中继承 C 库。 &lt;ctype.h&gt; 有一个函数int toupper(int)

C++ 继承了这个:

[n3290: 21.7/1]: 表 74、75、76、77、78 和 79 描述了标头 &lt;cctype&gt;, &lt;cwctype&gt;, &lt;cstring&gt;, &lt;cwchar&gt;, &lt;cstdlib&gt; (字符转换)和&lt;cuchar&gt;

[n3290: 21.7/2]: 这些标头的内容应与 标准 C 库头文件 &lt;ctype.h&gt;&lt;wctype.h&gt;&lt;string.h&gt;&lt;wchar.h&gt;&lt;stdlib.h&gt; 以及 C Unicode TR 标头&lt;uchar.h&gt;,分别为[..]

[n3290: 17.6.1.2/6]:在 C 中定义为函数的名称应 被定义为 C++ 标准库中的函数。

但不推荐使用&lt;ctype.h&gt;

[n3290: C.3.1/1]: 为了与标准 C 库兼容, C++ 标准库提供了 18 个 C 头文件 (D.5),但它们的用途是 在 C++ 中已弃用。

访问 C toupper 的方法是通过 C++ 向后兼容标头 &lt;cctype&gt;。对于此类标头,内容被移动或复制(取决于您的实现)到std 命名空间:

[n3290: 17.6.1.2/4]: [..] 然而,在 C++ 标准库中,声明 (除了在 C 中定义为宏的名称)在 命名空间标准的命名空间范围(3.3.6)。 未指定 这些名称是否首先在全局命名空间中声明 作用域,然后通过显式注入命名空间std 使用声明 (7.3.3)。

但是 C++ 库还在标头 &lt;locale&gt; 中引入了一个新的、特定于语言环境的函数模板,即 称为 toupper(当然,在命名空间 std 中):

[n3290: 22.2]: [..] template <class charT> charT toupper(charT c, const locale& loc); [..]

所以,当您使用std::toupper 时,有两个 重载可供选择。由于您没有告诉 GCC 您希望使用哪个函数,因此无法解决重载问题,并且无法完成对 std::transform 的调用。


差异

现在,那个原始问题的 OP 没有遇到这个问题。他可能在范围内没有std::toupper 的语言环境版本,但你也没有#include &lt;locale&gt;

但是:

[n3290: 17.6.5.2]: C++ 头文件可能包含其他 C++ 头文件。

所以 恰好你的 &lt;iostream&gt; 或你的 &lt;algorithm&gt;,或这些标题包含的标题,或 那些 标题包含的标题(等等) ,导致在您的实现中包含&lt;locale&gt;


解决方案

有两种解决方法。

  1. 您可以提供一个转换子句来强制函数指针引用您希望使用的重载:

    std::transform(
       s.begin(),
       s.end(),
       std::back_inserter(out),
       (int (*)(int))std::toupper  // specific overload requested
    );
    
  2. 您可以通过显式使用全局 toupper 从重载集中删除语言环境版本:

    std::transform(
       s.begin(),
       s.end(),
       std::back_inserter(out),
       ::toupper                  // global scope
    );
    

    但是,请记住,&lt;cctype&gt; 中的此功能是否可用是未指定的 ([17.6.1.2/4]),并且不推荐使用 &lt;ctype.h&gt; ([C.3.1/1])。

    因此,这不是我推荐的选项。


(注意:我讨厌把尖括号写成标题名称的一部分——它们是#include语法的一部分,而不是标题名称——但我已经在这里完成了为了与 FDIS 报价保持一致;老实说,它更清晰...)

【讨论】:

  • 注意: 这有点傻,因为std::transform 本身就是在寻找一个特定的函数签名,所以理论上编译器可以推断出解决方案 1 。但是,这很复杂。欢迎使用 C++。
  • 伟大的博主,我需要一些时间来理解所提到的许多观点。非常感谢!
  • @Stack:没问题!我花了一些时间来整理它们。 :P
  • 聚会有点晚了,但我仍然认为编译器应该至少列出模棱两可的选项。
  • ...看来这就是问题所在,或者至少是其中的一个重要部分。为了确定这是否是行为差异的原因,我编写了一个简单的程序,它读取一个字符串,用toupper() 对其进行转换,然后输出结果。它包括&lt;iostream&gt;&lt;string&gt;&lt;cctype&gt;&lt;locale&gt;&lt;algorithm&gt;。有趣的是,MSVC 2010 似乎能够正确推断出您想要哪个版本的toupper(),而更高版本(或至少 2015 年)则不能。只要您不明确包含&lt;locale&gt;,MSVC 2015 就可以,但如果您这样做,它就无法解决过载问题。
【解决方案3】:
std::transform(s.begin(), s.end(), s.begin(), 
std::bind(&std::toupper<char>, std::placeholders::_1, std::locale()));

如果你使用vc工具链,请包含locale

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多