Clang:将绑定或mem_fn与字符串::c_str和转换一起使用时出现问题

Clang: Trouble using bind or mem_fn with string::c_str and transform

本文关键字:转换 一起 问题 绑定 mem fn Clang 字符串 str      更新时间:2023-10-16

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

我发现如果我希望它同时使用 g++ 和 clang 跨平台正确链接,我需要使用 lambda 版本(选项 3)。我是否在 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)

这似乎是自 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 代码在 C++11 中&std::vector<X>::push_back停止编译,因为它现在是一个重载函数(有一个重载采用 const 左值引用,一个重载采用右值引用)。

这个具体的例子可能在实践中有效,因为没有实现会重载std::basic_string::c_str或给它一个有趣的签名。

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

[](const std::string& str) { return str.c_str(); }

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

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

似乎是一个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标志。

相关文章: