8

我试图了解如何使用std::tolower...

#include <iostream>
#include <string>
#include <algorithm>
#include <locale>

int main()
{
    std::string test = "Hello World";
    std::locale loc;
    for (auto &c : test)
    {
        c = std::tolower(c, loc);
    }

    std::transform(test.begin(), test.end(), test.begin(), ::tolower); // 1) OK
    std::transform(test.begin(), test.end(), test.begin(), std::tolower); // 2) Cryptic compile error
    std::transform(test.begin(), test.end(), test.begin(), static_cast<int(*)(int)>(std::tolower)); // 3) Cryptic compile error. Seems OK with other compilers though

    return 0;
}

所以:

  1. 为什么::tolower版本有效?
  2. 为什么std::tolower不能在 std::transform 中工作?
  3. static_cast<int(*)(int)>(std::tolower))真正想要做什么?为什么它适用于 GCC 而不适用于 Visual Studio 2013?
  4. 那么我如何std::lower在 std::transform 中使用 Visual Studio 2013 呢?
4

2 回答 2

9

首先,请注意,这些方法都没有以可移植的方式做正确的事情!问题是char可能已签名(通常是),但版本tolower()只接受正值!那是你真的想std::tolower()使用这样的东西:

std::transform(test.begin(), test.end(), test.begin(),
               [](unsigned char c) { return std::tolower(c); });

(或者,如果你被 C++03 卡住了,当然可以使用相应的函数对象)。使用std::tolower()(或::tolower()就此而言)负值会导致未定义的行为。当然,这只在char签名的平台上很重要,然而,这似乎是典型的选择。

要回答您的问题:

  1. 包含时,您通常会在命名空间和全局命名空间中<cctype>从标准 C 库中获取各种函数和类型。std因此,使用::tolower正常工作,但不能保证工作。
  2. 包含 时<locale>,有两个std::tolower可用版本,一个 asint(*)(int)和一个 as char(*)(char, std::locale const&)。仅std::tolower使用编译器时,通常无法决定使用哪一个。
  3. 由于std::tolower是模棱两可的,因此 using 可以消除使用static_cast<int(*)(int)>(std::tolower)哪个版本的歧义。为什么使用static_cast<...>()VC++ 失败,我不知道。
  4. 无论如何,您都不应该使用sstd::tolower()序列,char因为它会导致未定义的行为。使用std::tolower内部使用的函数对象unsigned char

值得注意的是,使用函数对象而不是函数指针通常快得多,因为内联函数对象很简单,但内联函数指针却没有那么简单。编译器在函数实际已知的情况下内联函数指针的使用越来越好,但当代编译器当然并不总是通过函数指针内联函数调用,即使所有上下文都在那里。

于 2013-11-09T14:10:52.787 回答
7

std::tolower在 C++ 中被重载,它被声明<cctype>

int tolower(int);

并且也<locale>作为

template<CharT> CharT tolower(CharT, const locale&);

因此,当您说“ std::tolower”时,您会得到对重载函数的模棱两可的引用。

  1. 为什么::tolower版本有效?

当您包含<cctype>单参数重载时,将在命名空间中声明,std可能在全局命名空间中声明,具体取决于编译器。如果你包含<ctype.h>,那么它保证被包含在全局命名空间中,并且::tolower会起作用(尽管请注意 Dietmar 关于何时不安全的观点)。两个参数重载 from<locale>从未在全局命名空间中声明,因此::tolower从不引用两个参数重载。

2. 为什么std::tolower在 std::transform 中不起作用?

见上文,这是一个重载的名称。

3.static_cast<int(*)(int)>(std::tolower))真正想要做什么?

它告诉编译器你想要int std::tolower(int)重载,而不是std::tolower.

为什么它适用于 GCC 而不适用于 Visual Studio 2013?

可能是因为您没有包含<cctype>,或者(不太可能)它可能是 Visual Studio 错误。

4. 那我如何std::lowerstd::transformVisual Studio 2013 中使用呢?

如果您知道您只有值介于 0 和 127 之间的字符,那么您可以包含<ctype.h>和使用::tolower(因为两个参数版本未在全局命名空间中声明,仅在命名空间中声明std)或使用静态转换消除您想要的重载。强制转换的替代方法是使用局部变量:

typedef int (*tolower_type)(int);
tolower_type tl = &std::tolower;
std::transform(b, e, b, tl);

一种更安全且可移植的替代方法是使用自定义函数对象(或 lambda 表达式)安全地调用所需的重载:

std::transform(b, e, b, [](unsigned char i) { return std::tolower(i); });

std::tolower与参数一起使用,因此编译器可以进行重载解析以判断您要调用哪个重载。该参数是unsigned char为了确保我们永远不会将char带有负值的 a 传递给tolower(int),因为它具有未定义的行为。

有关详细信息,请参阅http://gcc.gnu.org/onlinedocs/libstdc++/manual/strings.html#strings.string.simple 。

于 2013-11-09T14:09:29.713 回答