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