Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using C-style arrays in std::vector - MSVC bug?

I'm currently trying to get a legacy codebase to build under C++20, and I encountered something like this:

size_t someCount; // value comes from somewhere else
…
std::vector<const char *[2]> keyValues(someCount);

I can't trivially change this to something like std::vector<std:array<const char *, 2>> because this is later passed to some API outside of my control. The above abomination compiles fine with Clang and GCC, and even MSVC as long as I don't enable C++20, but it breaks in MSVC in C++20, as you can see here at Godbolt.

I assume this is related to the DefaultInsertable requirement on T if the above constructor is used (which is indeed the only requirement mandated by the standard). According to cppreference (see previous link), STL implementations up to C++17 used placement new to default-construct the elements, and starting in C++20, std::construct_at is being used for DefaultInsertable types. This may trigger the regression from C++17 to C++20 for MSVC.

The standard says that a type is DefaultInsertable if this expression is well-formed:

allocator_traits<A>::construct(m, p)

So in my case, that would be:

const char * dummy[2];
using Allocator = std::allocator<const char *[2]>;
Allocator a;
// This must be well-formed:
std::allocator_traits<Allocator>::construct(a, dummy);

This compiles fine and without warnings in GCC, Clang and MSVC, so I'll go ahead and assume that const char *[2] is a DefaultInsertable type. But that means that the constructor call in my first example should compile.

Is this an MSVC bug?

The compiler error is:

C:/data/msvc/14.34.31931-Pre/include\xutility(218): error C2440: 'return': cannot convert from 'const char **' to '_Ty (*)'
        with
        [
            _Ty=const char *[2]
        ]

C:/data/msvc/14.34.31931-Pre/include\xutility(218): note: Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or parenthesized function-style cast
C:/data/msvc/14.34.31931-Pre/include\xmemory(673): note: see reference to function template instantiation '_Ty (*std::construct_at<_Objty,,0x0>(_Ty (*const )) noexcept)' being compiled
        with
        [
            _Ty=const char *[2],
            _Objty=const char *[2]
        ]
like image 495
Lukas Barth Avatar asked Jan 01 '26 19:01

Lukas Barth


1 Answers

Before C++20, using array types in std::vector with the default allocator std::allocator was not supported, because std::allocator::destroy would try a simple (pseudo)-destructor call that is ill-formed for anything but class and scalar types. Thereby array types did not satisfy the Erasable requirement with the default allocator, which std::vector requires to be satisfied.

Since C++20 std::allocator is defaulted through std::allocator_traits to use std::destroy_at which in turn has a special case for array types that calls the destructor individually on each element. So Erasable is now satisfied with the default allocator.

However, C++20 also defaulted std::allocator's construct through std::allocator_traits to use std::construct_at. In contrast to the old std::allocator::construct, std::construct_at returns the result of the placement-new expression and has its return-type declared as T* instead of void. T* is also the type of the first argument, i.e. T is the element type.

The issue here is that the placement-new expression inside std::construct_at will not return a pointer-to-array for array types, but instead return a pointer to the first element of the array. If T is an array type U[N], then the return type of the new expression is therefore U*, not U(*)[N] as construct_at is declared to return. Therefore the construct_at instantiation is ill-formed and array types do not satisfy the DefaultInsertable requirement with the default allocator.

It is probably unintended that std::construct_at doesn't work with bounded array types. There is an open LWG issue 3436 for that.

like image 150
user17732522 Avatar answered Jan 03 '26 09:01

user17732522



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!