Let's say I have defined a zero_initialize()
function:
template<class T>
T zero_initialize()
{
T result;
std::memset(&result, 0, sizeof(result));
return result;
}
// usage: auto data = zero_initialize<Data>();
Calling zero_initialize()
for some types would lead to undefined behavior1, 2. I'm currently enforcing T
to verify std::is_pod
. With that trait being deprecated in C++20 and the coming of concepts, I'm curious how zero_initialize()
should evolve.
- What (minimal) trait / concept can guarantee memsetting an object is well defined?
- Should I use
std::uninitialized_fill
instead of std::memset
? And why?
- Is this function made obsolete by one of C++ initialization syntaxes for a subset of types? Or will it be with the upcoming of future C++ versions?
1) Erase all members of a class.
2) What would be reason for “undefined behaviors” upon using memset on library class(std::string)? [closed]
There is technically no object property in C++ which specifies that user code can legally memset
a C++ object. And that includes POD, so if you want to be technical, your code was never correct. Even TriviallyCopyable is a property about doing byte-wise copies between existing objects (sometimes through an intermediary byte buffer); it says nothing about inventing data and shoving it into the object's bits.
That being said, you can be reasonably sure this will work if you test is_trivially_copyable
and is_trivially_default_constructible
. That last one is important, because some TriviallyCopyable types still want to be able to control their contents. For example, such a type could have a private int
variable that is always 5, initialized in its default constructor. So long as no code with access to the variable changes it, it will always be 5. The C++ object model guarantees this.
So you can't memset
such an object and still get well-defined behavior from the object model.
What (minimal) trait / concept can guarantee memsetting an object is well defined?
Per the std::memset
reference on cppreference the behavior of memset
on a non TriviallyCopyable type is undefined. So if it is okay to memset
a TriviallyCopyable then you can add a static_assert
to your class to check for that like
template<class T>
T zero_initialize()
{
static_assert(std::is_trivial_v<T>, "Error: T must be TriviallyCopyable");
T result;
std::memset(&result, 0, sizeof(result));
return result;
}
Here we use std::is_trivial_v
to make sure that not only is the class trivially copyable but it also has a trivial default constructor so we know it is safe to be zero initialized.
Should I use std::uninitialized_fill
instead of std::memset
? And why?
You don't need to here since you are only initializing a single object.
Is this function made obsolete by one of C++ initialization syntaxes for a subset of types? Or will it be with the upcoming of future C++ versions?
Value or braced initialization does make this function "obsolete". T()
and T{}
will give you a value initialized T
and if T
doesn't have a default constructor it will be zero initialized. That means you could rewrite the function as
template<class T>
T zero_initialize()
{
static_assert(std::is_trivial_v<T>, "Error: T must be TriviallyCopyable");
return {};
}
The most general definable trait that guarantees your zero_initialize
will actually zero-initialize objects is
template <typename T>
struct can_zero_initialize :
std::bool_constant<std::is_integral_v<
std::remove_cv_t<std::remove_all_extents_t<T>>>> {};
Not too useful. But the only guarantee about bitwise or bytewise representations of fundamental types in the Standard is [basic.fundamental]/7 "The representations of integral types shall define values by use of a pure binary numeration system." There is no guarantee that a floating-point value with all bytes zero is a zero value. There is no guarantee that any pointer or pointer-to-member value with all bytes zero is a null pointer value. (Though both of these are usually true in practice.)
If all non-static members of a trivially-copyable class type are (arrays of) (cv-qualified) integral types, I think that would also be okay, but there's no possible way to test for that, unless reflection comes to C++.