Clang:麻烦使用bind或mem_fn与string :: c_str和transform

Rya*_*yan 5 c++ lambda stdbind c++11 libc++

试图将std :: string的向量转换为const char*的向量:

#include <algorithm>
#include <functional>
#include <string>
#include <vector>

int main(int argc, char** argv)
{
    std::vector<std::string> values;
    values.push_back("test1");
    values.push_back("test2");
    values.push_back("test3");

    std::vector<const char*> c_values(values.size());

    std::transform(values.begin(), values.end(), c_values.begin(), std::mem_fn(&std::string::c_str));
    std::transform(values.begin(), values.end(), c_values.begin(), std::bind(&std::string::c_str, std::placeholders::_1));
    std::transform(values.begin(), values.end(), c_values.begin(), [](const std::string& str) { return str.c_str(); });

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

使用g ++(4.7.2)进行编译时,所有三个选项都可以编译和链接.使用clang进行编译时,选项1和2无法链接,生成:

$ clang -std=c++11 -stdlib=libc++ -lc++ stringtransform.cpp 
Undefined symbols for architecture x86_64:
  "std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::c_str() const", referenced from:
    _main in stringtransform-ff30c1.o
ld: symbol(s) not found for architecture x86_64
Run Code Online (Sandbox Code Playgroud)

我发现我需要使用lambda版本(选项3),如果我希望它使用g ++和clang在平台之间正确链接.我是否遇到了链接器错误或者clang的C++ 11支持中的漏洞,或者我是如何调用mem_fn()和bind()版本的?

编辑:

最新的Xcode(6.3.2,clang版本6.1.0)仍然存在错误:

$ clang -v
Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn)
Run Code Online (Sandbox Code Playgroud)

Jon*_*ely 2

这似乎是自 LLVM 3.6.0 以来 libc++ 版本中的一个错误

我的猜测是他们没有std::string::c_str()从 DSO 中导出符号,因此该符号不是全局的并且无法链接到。

指向成员函数的指针&string::c_str创建对该函数符号的依赖关系,链接器无法解析该依赖关系,因为符号的定义不是全局的。它有时在优化时有效,因为c_str()函数被内联,并且不需要符号的外部定义。

您可以通过在代码中自行实例化该函数来解决此问题:

#ifdef _LIBCPP_VERSION
template const char* std::string::c_str() const;
#endif
Run Code Online (Sandbox Code Playgroud)

但是您应该意识到您的代码有问题。选项 1 和 2 不保证适用于任何标准库实现:

std::mem_fn(&std::string::c_str)
std::bind(&std::string::c_str, std::placeholders::_1)
Run Code Online (Sandbox Code Playgroud)

对于像标准库这样的非虚拟成员函数,std::basic_string::c_str()可以自由定义额外的重载,或者使用与标准中指定的签名不同的签名。这意味着任何尝试&std::a_class::a_nonvirtual_member_function都是不可移植的,并且可能是一个错误。

例如,许多 C++98 代码&std::vector<X>::push_back在 C++11 中停止编译,因为它现在是一个重载函数(有一个采用 const 左值引用的重载和一个采用右值引用的重载)。

这个特定的示例可能会在实践中起作用,因为没有实现过载std::basic_string::c_str或给它一个有趣的签名。

lambda 函数很好,因为它不获取成员函数的地址,它只是调用它:

[](const std::string& str) { return str.c_str(); }
Run Code Online (Sandbox Code Playgroud)

这样,编译器使用重载解析来查找函数,而不是通过指向成员函数的可疑指针。