13

I'm writing some C++11 code that makes assumptions about the nature of std::string that are valid, but represent behavior that was changed in C++11. In the earlier days, libstdc++'s basic_string implementation conformed to the 98/03 requirements, but not to the more strict C++11 requirements.

As I understand it, libstdc++ has fixed the issues around basic_string. The problem is that there are many versions of the library that people use which do not implement this fix. And my code may silently fail in many unpleasant ways on them.

I would like to have a static_assert fire if the user attempts to compile my library against those non-conformant versions of libstdc++. How do I detect the version, and equally importantly, which version should I look for?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 1
    can you voice your assumptions? – Tobias Langner Dec 19 '15 at 22:17
  • 1
    I believe that is required even in C++03. COW should retain this property. – Puppy Dec 19 '15 at 22:22
  • @Puppy: Sorry, I said that wrong. It's better to just [look here](http://stackoverflow.com/a/29199733/734069). Something like that. – Nicol Bolas Dec 19 '15 at 22:25
  • Could you check if a `std::string` is null-terminated? If it is, you know it conforms to C++11 requirements as per C++0x FDIS 21.4.7.1/1. – erip Dec 19 '15 at 22:25
  • @erip: That's not something you can `static_assert` on. Also, in C++98/03, it would be undefined behavior to try to access the terminator character, so it might still work even on them. – Nicol Bolas Dec 19 '15 at 22:27
  • @NicolBolas why do you need a static_assert? I know they are fine - but you probably need unit-tests as well so running the test there should be good as well. – Tobias Langner Dec 19 '15 at 22:28
  • 1
    @TobiasLangner: Because most of my users will be compiling my *library*, not my tests. – Nicol Bolas Dec 19 '15 at 22:32
  • @NicolBolas: That is not equivalent- the call to `s[0]` in that answer is non-`const`-qualified. – Puppy Dec 19 '15 at 22:33
  • @NicolBolas then add the test to initialization in Debug-Mode. Should not take long. Take the example from the link above and do it both ways to detect COW. – Tobias Langner Dec 19 '15 at 22:34
  • @Puppy: I never said the link was equivalent; that's why I said "I said that wrong." – Nicol Bolas Dec 19 '15 at 22:35
  • @TobiasLangner: Um, thanks, but no thanks. – Nicol Bolas Dec 19 '15 at 22:36
  • 1
    @Tobias: You're making an assumption that his library actually *has* some one-time initialization. – Puppy Dec 19 '15 at 22:54

2 Answers2

10

The new C++11 compliant std::string was introduced with the new (dual) ABI in GCC 5 (Runtime Library Section of the changelog).

The macro _GLIBCXX_USE_CXX11_ABI decides whether the old or new ABI is being used, so just check it:

#if _GLIBCXX_USE_CXX11_ABI

Of course that's specific to libstdc++ only.

AliciaBytes
  • 7,300
  • 6
  • 36
  • 47
5
#include <string>

static_assert(sizeof(std::string) != sizeof(void*), "using ref-counted string");

int
main()
{
}

Demo: http://melpon.org/wandbox/permlink/P8LB79Cy6ASZlKuV

This test takes advantage of the internal workings of all of the known std::lib implementations of std::string, and of gcc's implementation in particular.

gcc's refcounted string consists of a single pointer to a dynamically allocated structure that holds the size, capacity, reference count, and data of the string. Scott Meyers does a nice summary of string implementations in Effective STL that was accurate in the 2001 time frame. I believe (I could be mistaken) that "implementation C" in item 15 of that book is gcc's std::string.

For short-string implementations (pretty much mandated by C++11), a string can no longer consist of a single pointer on the stack. Scott's implementation D is our first look at a short-string implementation from that era. This is the VS/Dinkumware string. The sizeof(string) itself will contain some data buffer to hold string data with no allocations.

One can get a handle on what different implementations are doing with this short program:

#include <iostream>
#include <string>

int
main()
{
    std::string s;
    std::cout << "word size is           " << sizeof(void*)/sizeof(char) << '\n';
    std::cout << "sizeof string is       " << sizeof(s) << '\n';
    std::cout << "short string buffer is " << s.capacity() << '\n';
}

This prints out the word size, typically 4 or 8 (32 bit / 64 bit) as at least one implementation (libc++) changes its characteristics on this hardware feature. Then it prints out the sizeof(string) which will be a multiple of word size, and then the capacity() of an empty string, which will be the size of the short-string buffer if it exists.

Here is a somewhat incomplete survey:

gcc/libstdc++ 4.8

word size is           8
sizeof string is       8
short string buffer is 0

gcc/libstdc++ 5.2

word size is           8
sizeof string is       32
short string buffer is 15

clang/libc++ -arch i386 OS X

word size is           4
sizeof string is       12
short string buffer is 10

clang/libc++ -arch x86_64 OS X

word size is           8
sizeof string is       24
short string buffer is 22

VS-2015

word size is           4
sizeof string is       24
short string buffer is 15

In this survey, only gcc/libstdc++ 4.8 is clearly not using a short-string optimization. And only gcc/libstdc++ 4.8 has sizeof(string) == 1 word. And this is in fact the only implementation in this survey that is using reference counting.

All in all, this test for libstdc++'s std::string isn't portable. But by specification it doesn't have to be. We can take advantage of the known history of gcc's development in this area. The spec (the question) says it only has to work on gcc's libstdc++.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • Can you explain briefly why would this do the trick? Do COW strings have only one `void*` (private) data member and of course no virtual functions? – vsoftco Dec 20 '15 at 03:29
  • Thanks, great explanation. – vsoftco Dec 20 '15 at 04:32
  • To extend the survey, VS-2013 strings have the same structure as VS-2015's. In VS-2010 strings are an extra word size bigger (due to not optimising away the zero size std::allocator). I put together a [summary table](http://info.prelert.com/blog/cpp-stdstring-implementations#summarytable) for the 64 bit versions of the different standard libraries, that also includes other details like the growth strategy. – dmr195 Feb 07 '16 at 11:50