5

Trying to convert a vector of std::string to a vector of 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;
}

When compiling with g++ (4.7.2), all three options compile and link fine. When compiling with clang, options 1 and 2 fail to link, producing:

$ 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

I am finding I need to use the lambda version (option 3) if I want it to link correctly across platforms using both g++ and clang. Am I running into a linker bug or a hole in clang's C++11 support, or is there something wrong with how I'm invoking the mem_fn() and bind() versions?

EDIT:

Error still present on latest Xcode (6.3.2, with clang version 6.1.0:

$ clang -v
Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn)
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
Ryan
  • 231
  • 1
  • 14
  • I don't know about you, but for me your examples don't even compile with `clang` (much less link), as `std::bind` hasn't been implemented in my `XCode/*/include/c++` directory. What version of clang are you using? – user14717 Jan 01 '15 at 01:14
  • I don't have an answer unfortunately, but here is a simpler example that reproduces what you talk about, https://gist.github.com/anonymous/985aa0da6589e155a27f . It is also fix by using -Os – tahsmith Jun 17 '15 at 23:42
  • @user14717 note the `-stdlib=libc++` option, which uses the libc++ standard library instead of the ancient version of libstdc++ from 2007 that comes with OS X. It should compile if you use libc++ or install a modern version of libstdc++ (e.g. via homebrew) and use that. – Jonathan Wakely Apr 04 '17 at 12:46

3 Answers3

2

I found a workaround: Compiling your code with -Os makes the issue go away.

Jason Prado
  • 1,504
  • 2
  • 12
  • 21
2

This seems to be a bug in libc++ versions since LLVM 3.6.0

My guess is they didn't export the std::string::c_str() symbol from their DSO, so the symbol is not global and can't be linked to.

The &string::c_str pointer to member function creates a dependency on the symbol for that function, which can't be resolved by the linker because the definition of the symbol is not global. It sometimes works when optimising because the c_str() function gets inlined, and no external definition for the symbol is needed.

You can workaround it by instantiating the function yourself in your code:

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

However you should be aware that your code has a problem. Options 1 and 2 aren't guaranteed to work with any standard library implementation:

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

For non-virtual member functions like std::basic_string::c_str() the standard library is free to define additional overloads, or to use different signatures from those specified in the standard. This means any attempt to do &std::a_class::a_nonvirtual_member_function is non-portable, and potentially a bug.

For example, lots of C++98 code that did &std::vector<X>::push_back stopped compiling in C++11 because that is now an overloaded function (there's an overload taking a const lvalue reference and an overload taking an rvalue reference).

This specific example will probably work in practice, because no implementations overload std::basic_string::c_str or give it a funny signature.

The lambda function is fine, because that doesn't take the address of a member function, it just calls it:

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

This way the compiler finds the function using overload resolution, not via a dubious pointer to member function.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
0

This seems to be a libc++ problem.

Given this,

#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;
}

We get the same error.

It is fixed by using -stdlib=libstdc++ -lstdc++. It seems to work for any -On flag.

tahsmith
  • 1,643
  • 1
  • 17
  • 23