Say you have a type that is trivially copyable, not an aggregate, and not trivially constructible:
struct Foo
{
Foo() = default;
Foo(int i)
: a(i)
{};
int a = 5;
};
Foo isn't an aggregate because it has a user-declared constructor, it isn't trivially constructible because it has user-defined initialisers, and it is trivially copyable and trivially destructible. It isn't a trivial type.
Is it legal to attempt to implicitly construct such a type via memcpy?
Foo* makeFooCopy(const Foo& src)
{
// Assume alignment isn't a problem
auto ptr = malloc(sizeof(Foo));
memcpy(ptr, &src, sizeof(Foo));
return reinterpret_cast<Foo*>(ptr);
}
cppreference says that an implicit lifetime type "...has at least one trivial eligible constructor and a trivial, non-deleted destructor." (the aggregate case does not apply here). But it's not clear to me what the "trivial eligible constructor" is here; must it be a default constructor (i.e. this is just stating that the type needs to be trivially default constructible) or is the ability to trivially copy the object sufficient?
The motivating issue is a vector-like type in our code; profiling shows that in a specific use case a significant amount of our run time consists of copying contiguous containers of trivially copyable but not trivially default constructible types into our vector-like type, which is currently implemented as a loop around emplace_back. We would like to just use memcpy to copy the entire buffer, like so:
template<MemcpyableContainer C>
SpecialVectorType(const C& container)
{
resize(std::size(container));
memcpy(our_storage, std::addressof(*std::begin(container)), std::size(container) * sizeof(element_type))
}
but our compiler isn't optimising out the placement new calls in resize. It's not clear to me if it's legal to elide them.
"At least one trivial eligible constructor" means any of the three constructors(default constructor/copy constructor/move constructor) is trivial.
The type trait std::is_implicit_lifetime is added in C++23.
Here is the implementation in the related draft p2674
template <typename T>
struct is_implicit_lifetime
: std::disjunction<
std::is_scalar<T>, std::is_array<T>, std::is_aggregate<T>,
std::conjunction<
std::is_trivially_destructible<T>,
std::disjunction<std::is_trivially_default_constructible<T>,
std::is_trivially_copy_constructible<T>,
std::is_trivially_move_constructible<T>>>> {};
There's no doubt class Foo
is an implicit-lifetime type. It is well defined to implicitly create such objects via memcpy
.