为什么std::tolower不能用于std::transform
在C++中,std::tolower和std::toupper函数(在本文中都用std::tolower指代两者)用于对一个字符进行大小写转换,将其与std::transform函数结合则可以对整个字符串进行大小写转换,如下所示:
1 | std::string string = "ABCDEF"; |
以上代码在Visual C++下能编译成功,但是在XCode下却编译失败,错误信息为No matching function for call to 'transform'
。这是因为std::tolower函数存在两个重载,第一个定义于头文件cctype,声明如下:
1 | int tolower(int ch); |
第二个定义于头文件locale,声明如下:
1 | template<class charT> |
cctype和locale都是很基础的模块,会被其它头文件引用,因此这两个重载通常都会同时出现。XCode的编译器由于不知道该选择哪个重载而报错。
然而,从理论上来说,编译器是可以知道如何选择的。std::transform在XCode中的源码如下:
1 | template <class _InputIterator, class _OutputIterator, class _UnaryOperation> |
在第七行,可以看到调用__op
的时候只传了一个参数,因此无论如何只能选择std::tolower的第一个重载,编译器完全有能力做出这个推导。如果把上述std::transform的源码转移到Visual C++并且以同样的方式来调用,可以编译成功,可见这个推导是可行的。那为什么XCode的编译器没有这么做呢?一个可能的原因是效率问题,类型推导越精确会耗费越多编译时间。从这一点或许可以解释为什么Visual C++的编译速度这么慢。
那么,应该如何解决这个问题呢?有两种方法,第一种方法是把std::tolower换成::tolower,如下所示:
1 | std::transform( |
默认情况下,在全局名称空间中,tolower只有一种声明形式,因此没有问题。但前提是没有使用using namespace
语句把std名称空间的内容导入全局名称空间——然而这种用法很常见,因此使用::tolower并不一定有效。
第二种方法是显式指定使用std::tolower的第一个重载,如下所示:
1 | std::transform( |
这种做法带来了编码负担,因为每次使用的时候都要回忆一下std::tolower的声明形式,并且要输入更多字符。所以最好用一个函数把它封装起来,一劳永逸。