13

I'm not sure if I'm too naïve or simply too unknowing.

But why does the following differ?

constexpr auto nInitialCapacity1 = std::wstring().capacity();
const auto     nInitialCapacity2 = std::wstring().capacity();

In Visual Studio 2022/17.0.5 the code above results in:

nInitialCapacity1 = 8
nInitialCapacity2 = 7

Why is the result of the constexpr (compile time) version not equal to the const version of the call?

Thanks for any explanation!

Boann
  • 48,794
  • 16
  • 117
  • 146
Martin Lemburg
  • 507
  • 2
  • 12
  • 7
    I don't see any reason that the standard library needs to implement this the same way at runtime and at compile-time. The capacity of a default-constructed string is up to the implementation anyway. – user17732522 Feb 01 '22 at 19:20
  • 1
    @user17732522 You are quite right. All implementation specific! But why should the compile-time version differ from the other? No matter, what numbers will be returned by an implementation, personally I'd expect the same result. – Martin Lemburg Feb 01 '22 at 19:26
  • 3
    Maybe they cannot do SSO in the usual way, because memory cannot be retyped in constant expressions, although a union-based implementation should also work in constant expressions. – user17732522 Feb 01 '22 at 19:28
  • `constexpr auto nInitialCapacity1 = std::wstring().capacity();` doesn't work in `Clang` or `GCC` with `-std=c++20` or `-std=c++2a`. `error: constexpr variable 'nInitialCapacity1' must be initialized by a constant expression` – Brandon Feb 01 '22 at 19:40
  • @Brandon libstdc++ (gcc) supports it only since version 12 (i.e. current trunk) and libc++ (clang) doesn't have support for it yet. But it is supposed to be possible in C++20. See https://en.cppreference.com/w/cpp/compiler_support ("constexpr std::string") – user17732522 Feb 01 '22 at 19:47
  • @user17732522; I just tried it in `gcc-trunk-20220201/include/c++/12.0.1`. Same thing: https://godbolt.org/z/dW96G8dv5 – Brandon Feb 01 '22 at 19:56
  • 3
    @Brandon [add `-std=c++20` and works fine.](https://godbolt.org/z/nj1zjK3j5) – IlCapitano Feb 01 '22 at 19:57
  • You sure you had `const` in front of `nInitialCapacity2`? That should not make a difference. Non-`const` would. – Barry Feb 02 '22 at 19:52

2 Answers2

21

Microsoft's STL disables short string optimisation in constant evaluated contexts, so it allocates memory instead.

The allocations are always one more than a power of two, so the capacity (which excludes the last L'\0') is always a power of two.

In the non-constant-evaluated version, the short string buffer can hold 8 characters, one of which is a L'\0', so the capacity is 7.

Artyer
  • 31,034
  • 3
  • 47
  • 75
  • 1
    `is_constant_evaluated()` should be `true` for both cases though. – Barry Feb 02 '22 at 18:25
  • 1
    @Barry Yes in this case because it's a `const std::size_t`, it would be initialized by a constant expression, and `const auto x = std::wstring().capacity();` does give `x = 8` (in my version of visual studio), but I'm assuming the original asker didn't have a manifestly constant-evaluated expression given the title of their question – Artyer Feb 02 '22 at 18:59
  • Yesterday a colleague and I found out that in VS2202 the debugger visualized in the watch in the tooltip of nInitialCapacity2 the number 7, but in the disassembly window we saw the value 8. Even using TRACE1 for output, printed out 7 not 8. The hypothesis of my colleague is, that the debugger, not knowing about the different behavior of const(expr) or non-conts(expr) calls, just calls the constexpr declared method non-conts(expr) since it doesn't have side effects. In this case the debugger gets a 7 and visualizes the 7 as value while debugging. We have been really surprised by this behavior! – Martin Lemburg Feb 03 '22 at 09:59
0

Update by new experiences and observations:

  • The contents of the disassembly window showed, that the result of the const-call to std::wstring::capacity is 8!
  • But the watch window and the tooltip of the variable show 7.

The hypothesis of a colleague is, that the debugger calls the constexpr method capacity non-const, gets the differing result of 7 and visualizes it.

A reason to look into the disassembly window was the unexpected behavior in the following code:

const auto nInitCap = std::wstring().capacity();
const auto nCap     = str.capacity();

if (nCap != nInitCap)
    std::wcout << "capacity " << nCap << "is not equal to the initial capacity " << nInitCap << std::endl;

if (nCap > nInitCap)
    std::wcout << "capacity " << nCap << "is greater than the initial capacity " << nInitCap << std::endl;

The debugger showed for the variables:

nInitCap: 7
nCap:     7

But the code printed out:

capacity 7 is not equal to the initial capacity 7

The capacity call to the const-constructed temporary object returns 8, as to be seen in the disassembly, so the behavior is explainable, even if the debugger of VS2022 17.0.5 shows 7

Martin Lemburg
  • 507
  • 2
  • 12