Creating an non constructible object: shouldn't std::bit_cast be constrained to implicit lifetime types?

3 days ago 3
ARTICLE AD BOX

It is possible to design non-constructible classes, but I managed to instantiate an object of such a class, using the fact that std::bit_cast "only" requires objects of trivially copyable type on input and output.

Let's recall the context of my previous question Creating an non constructible object: issue with trivially copyable definition? where my goal is not, here, to build a "construct-proof" class but to understand why, while having a supposedly one (class with no (public) constructor), doesn't prevent objects creation for this class altogether. It is possible to design non-constructible classes, but I managed to instantiate an object of such a class: I stumbled on this issue why trying to show that trivially copyable does not imply implicit lifetime. Jarod42 hinted toward a class with assignment operator but no constructor:

#include <bit> #include <cstring> #include <iostream> // Not constructible // Not implicit lifetime struct Test { int val{1}; Test() = delete; Test(Test const&) = delete; Test(Test&&) = delete; Test& operator=(Test const&) = default; Test& operator=(Test&&) = default; ~Test() = default; }; int main() { char storage[sizeof(Test)]; int i{666}; std::memcpy(storage,&i,sizeof(Test)); auto HeWhoMustNotBeNamed = std::bit_cast<Test>(storage); std::cout << HeWhoMustNotBeNamed.val; }

LIVE

All tested compilers (gcc, clang and msvc) recognize Test as trivially copyable. Test and storage fulfill the preconditions of std::bit_cast, therefore clang and msvc create an object of a non-constructible class, without undefined behavior. In my opinion, such behavior is undesirable as it violates the class semantic. I think that the issue lies in the definition of trivially copyable, which allows for classes that cannot have instances (though it's a syllogism that all objects that cannot exist can be trivially copied).

It can be noted that std::start_lifetime_as mandates its output to be an implicit lifetime (and even an aggregate which seems overkill if I got it right). Therefore, I would have found logical that std::bit_cast only accepts implicit lifetime type on output, while the need to constraint the input type is debatable, as there are already many constraint on the obtain value output.

Is there something wrong with my analysis?


Note: the question is not entirely theoretical as the non-constructible class can be a base implementation class that must be used only as inherited:

// Not constructible // Not implicit lifetime struct BaseImpl { int val{1}; BaseImpl() = delete; BaseImpl(BaseImpl const&) = delete; BaseImpl(BaseImpl&&) = delete; BaseImpl& operator=(BaseImpl const&) = default; BaseImpl& operator=(BaseImpl&&) = default; ~BaseImpl() = default; protected: BaseImpl(int i) : val{i} {}; }; struct Concrete: private BaseImpl { Concrete(int i) : BaseImpl{i}{}; int Value() const {return BaseImpl::val;}; };

LIVE

Read Entire Article