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

时间:2014-12-28 20:43:56

标签: c++ c++11 lambda libc++ stdbind

尝试将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;
}

使用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

我发现我需要使用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)

3 个答案:

答案 0 :(得分:2)

我找到了一种解决方法:使用-Os编译代码会使问题消失。

答案 1 :(得分:2)

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

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

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

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

#ifdef _LIBCPP_VERSION
template const char* std::string::c_str() const;
#endif

然而您应该知道您的代码存在问题。选项1和2不能保证与任何标准库实现一起使用:

std::mem_fn(&std::string::c_str)
std::bind(&std::string::c_str, std::placeholders::_1)

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

例如,许多C ++ 98代码在{+ 1}}中停止了在C ++ 11中的编译,因为它现在是一个重载函数(这是一个带有const左值引用和重载的重载采用右值参考)。

这个具体的例子可能在实践中起作用,因为没有实现重载&std::vector<X>::push_back或给它一个有趣的签名。

lambda函数没问题,因为它没有取成员函数的地址,只是调用它:

std::basic_string::c_str

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

答案 2 :(得分:0)

这似乎是一个libc ++问题。

鉴于此,

#include <string>
#include <iostream>

using namespace std;

int main(int argc, char** argv)
{
  string s("Hello, World!");
  const char * (std::string::*mem_c_str) () const = &std::string::c_str;
  cout << (s.*mem_c_str)() << endl;
  return 0;
}

我们得到了同样的错误。

使用-stdlib=libstdc++ -lstdc++修复此问题。它似乎适用于任何-On标志。