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]
]
std::vector<const char *[2]>
, or no soup for you? – SpecialismORB_init(…, const char* options[][2]=0)
. Thestd::vector
is used to dynamically build the argument. We could of course also manually allocate that withnew
, but I'd rather not make it worse… – Stlouisconst char *[2]
s. Given the situation, I would not object, too much, to usingstd::array
, and then usingreinterpret_cast
to make the whole problem go away. – Specialismstd::vector<std::array<const char *, 2>>
and thenreinterpret_casting
itsdata()
toconst char *[2]
? That has to be UB, right? This feels like a very clear violation of the strict aliasing rule. Also, we have no guarantees about the memory layout ofstd::array
, right? – Stlouisstd::array
is explicitly spelled out. There are some pretty good guarantees about it. This most likely violates strict aliasing, but I see it as the lesser of all evils, and this is what unit tests are for. – Specialismarray
could have padding at the end. Anarray<char, 2>
may be 4-bytes in size. You couldstatic_assert
to ensure that a particulararray
instantiation doesn't have padding though. – Claudieclaudinalanguage-lawyer
, so 'This is UB, so what?' is not what I'm going for. I try to write well-formed C++ programs. Everything else is completely unmaintainable in my experience. YMMV. – Stlouis'initializing': cannot convert from 'const char **' to 'T (*)'
. Apparentlynew T()
returns aconst char**
whenT
is aconst char*[2]
, which makes little sense to me, but is the crux of the issue – Cervine