Nick
Nick

Reputation: 10539

std::to_chars compile but not linking on MacOS / clang

I have problem compiling a project under MacOS with clang.

I did "pinpoint" the problem inside charconv header:

#include <charconv>
#include <array>
#include <iostream>

int main(){
    std::array<char, 64> buffer;

    auto[p, ec] = std::to_chars(buffer.begin(), buffer.end(), 123);

    if (ec != std::errc() )
        std::cout << "error" << '\n';

    std::cout << (const char *) buffer.data() << '\n';
}

Here is how I am compiling it.

Nikolays-MacBook-Air:~ nmmm$ clang --version
Apple LLVM version 10.0.1 (clang-1001.0.46.4)
Target: x86_64-apple-darwin18.5.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

Nikolays-MacBook-Air:~ nmmm$ clang -std=c++17 x.cc -lstdc++
Undefined symbols for architecture x86_64:
  "std::__1::__itoa::__u32toa(unsigned int, char*)", referenced from:
      std::__1::__itoa::__traits_base<unsigned int, void>::__convert(unsigned int, char*) in x-9b1746.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Nikolays-MacBook-Air:~ nmmm$ 

Any help will be appreciated.

update

I tried clang++, but it gives me same error message.

Upvotes: 2

Views: 2090

Answers (3)

Evan Balster
Evan Balster

Reputation: 11

Dropping this code into your project will allow the use of to_chars (but not from_chars) with the stock libc++ on older versions of Mac OS — no need to build the standard library yourself. I don't remember where I got this code or if I wrote it myself... Essentially it's a correct implementation of the functions Apple forgot to add to the ABI.

Macros to enable this based on deployment target are left as an exercise for the reader. This code won't compile/link properly against a deployment target of 10.15 or higher, or on Apple Silicon.

// Extra code to enable std::to_chars / from_chars
static inline void reverseString(char *begin, char *end)
{
    --end;
    while (begin < end) {std::swap(*begin, *end); ++begin; --end;}
}

namespace std::__itoa {
_LIBCPP_FUNC_VIS char* __u64toa(uint64_t __value, char* __buffer)
{
    char *start = __buffer;
    do
    {
        *(__buffer++) = '0' + (__value%10);
        __value /= 10;
    }
    while (__value);
    
    reverseString(start, __buffer);
    
    //*__buffer = '\0';
    return __buffer;
}
_LIBCPP_FUNC_VIS char* __u32toa(uint32_t __value, char* __buffer)
{
    char *start = __buffer;
    do
    {
        *(__buffer++) = '0' + (__value%10);
        __value /= 10;
    }
    while (__value);
    
    reverseString(start, __buffer);
    
    //*__buffer = '\0';
    return __buffer;
}
}

Upvotes: 1

Quuxplusone
Quuxplusone

Reputation: 27324

The <charconv> header contains declarations and uses of std::__1::__itoa::__u32toa, a function which is not defined in that header. The function is defined in libc++.dylib. However, Apple does not ship libc++.dylib with the same frequency as it ships headers; and, possibly more important, libc++ failed to include the new symbol in its "abilist" for Apple until 2018-09-23, even though the header change landed on 2018-08-01. So there was a period of about 53 days where (this aspect of) libc++ just didn't work on OSX. Maybe Apple picked it up and shipped it during that period.

One way to make your code work is to build libc++ yourself. Instructions here, as of late 2019.

$ clang++ -std=c++17 test.cc $ROOT/llvm-project/build/lib/libc++.dylib \
    -Wl,-rpath,$ROOT/llvm-project/build/lib
$ ./a.out
123

Some commenters on your question are suggesting that you could make your code work by using GNU libstdc++ instead of LLVM libc++. That's vaguely true, but I suspect that building libstdc++ for OSX will be even harder than building libc++ for OSX. (And the latter is not simple!) I am not aware of any way to install libstdc++ on OSX. There is brew install gcc@8, but I bet GCC 8 didn't have <charconv> either.

As of early 2020, no vendor (except just recently Microsoft) provides a full implementation of <charconv> — the floating-point to_chars and from_chars turn out to be difficult, so most vendors don't provide them. ("Why didn't they just copy an existing implementation?" Well, it turns out that <charconv> was standardized as part of C++17 before any implementation existed. Nobody guessed that it would be difficult!)

Upvotes: 4

n314159
n314159

Reputation: 5095

I wrote a nice long answer to this, to then check and see that it works for me (clang 9.0.0 on linux). You compile with clang and not clang++. The long answer below:

Clang's libc++ has not fully implemented the "Elementary string conversions, revision 5 " (as in to_chars and from_chars) for a long time and some parts are still outstanding, see here. But I think yours should work, so maybe you should update your library.

For usage of to_chars please consider, that it explicitly adds no \0 at the end of the string. Therefore you cannot just use std::cout << (const char *) buffer.data();. You have to use the pointer (named p in your code) to mark the end of your string. Either by inserting a \0, by generating a std::string_view sv(buffer.data(), static_cast<size_t>(p-buffer.data());, using std::copy(buffer.begin(), p, std::ostream_iterator<char>(std::cout, "")); or another of far too many ways.

Upvotes: 0

Related Questions