Date: Thu, 30 Nov 2023 10:17:50 +0000
Following on from the discussion about "std::optional<T>::abandon",
I'd like to discuss whether it would be helpful to be able to know at
compile time how much trailing padding there is inside a type? So we
could have two new standard library functions as follows:
namespace std {
template<typename T>
consteval size_t trailing_padding_size(void) noexcept
{
return compiler_magic;
}
template<typename T>
consteval size_t sizeof_minus_trailing_padding(void) noexcept
{
return sizeof(T) - trailing_padding_size<T>();
}
}
This would allow us to write our own 'std::optional' that places the
boolean flag inside T's trailing padding. Like for example in the
following code:
https://godbolt.org/z/exfbxqqe3
And here it is copy-pasted:
#include <cstddef> // size_t
#include <new> // ::new(void*)
#include <type_traits> // is_trivially_copyable
#include <utility> // forward
class A {
unsigned i;
char unsigned ch;
public:
A(void) : i(0), ch(0) {}
A(A const &rhs) { this->i = rhs.i + 1u; this->ch = rhs.ch + 1u; }
};
static_assert(false == std::is_trivially_copyable_v<A>); // Because
we implemented copy-constructor
static_assert(sizeof(A) == sizeof(unsigned)*2u);
namespace std {
template<typename T>
consteval size_t trailing_padding_size(void) noexcept
{
return -1;
}
template<>
consteval size_t trailing_padding_size<A>(void) noexcept
{
return sizeof(unsigned) - 1u;
}
template<typename T>
consteval size_t sizeof_minus_trailing_padding(void) noexcept
{
return sizeof(T) - trailing_padding_size<T>();
}
}
template<typename T>
class MyOptional {
static_assert(false == std::is_trivially_copyable_v<T>);
[[no_unique_address]] alignas(T) char unsigned
buf[std::sizeof_minus_trailing_padding<T>()];
bool is_alive = false;
public:
template<typename... Params>
constexpr T &emplace(Params&& ...args) // g++ won't accept
'Params... &&args'
{
if ( this->is_alive ) static_cast<T*>(static_cast<void*>(buf))->~T();
this->is_alive = false;
T &retval = *::new(buf) T( std::forward<Params>(args)... );
this->is_alive = true;
return retval;
}
~MyOptional(void)
{
if ( false == this->is_alive ) return;
static_cast<T*>(static_cast<void*>(buf))->~T();
}
constexpr T& value(void) noexcept { return
*static_cast<T*>(static_cast<void*>(buf)); }
constexpr void abandon(void) noexcept { this->is_alive = false; }
constexpr void reset() noexcept
{
if ( this->is_alive ) static_cast<T*>(static_cast<void*>(buf))->~T();
this->is_alive = false;
}
};
int main(void)
{
MyOptional<A> monkey;
monkey.emplace();
static_assert( sizeof(A) == sizeof(MyOptional<A>) );
}
I'd like to discuss whether it would be helpful to be able to know at
compile time how much trailing padding there is inside a type? So we
could have two new standard library functions as follows:
namespace std {
template<typename T>
consteval size_t trailing_padding_size(void) noexcept
{
return compiler_magic;
}
template<typename T>
consteval size_t sizeof_minus_trailing_padding(void) noexcept
{
return sizeof(T) - trailing_padding_size<T>();
}
}
This would allow us to write our own 'std::optional' that places the
boolean flag inside T's trailing padding. Like for example in the
following code:
https://godbolt.org/z/exfbxqqe3
And here it is copy-pasted:
#include <cstddef> // size_t
#include <new> // ::new(void*)
#include <type_traits> // is_trivially_copyable
#include <utility> // forward
class A {
unsigned i;
char unsigned ch;
public:
A(void) : i(0), ch(0) {}
A(A const &rhs) { this->i = rhs.i + 1u; this->ch = rhs.ch + 1u; }
};
static_assert(false == std::is_trivially_copyable_v<A>); // Because
we implemented copy-constructor
static_assert(sizeof(A) == sizeof(unsigned)*2u);
namespace std {
template<typename T>
consteval size_t trailing_padding_size(void) noexcept
{
return -1;
}
template<>
consteval size_t trailing_padding_size<A>(void) noexcept
{
return sizeof(unsigned) - 1u;
}
template<typename T>
consteval size_t sizeof_minus_trailing_padding(void) noexcept
{
return sizeof(T) - trailing_padding_size<T>();
}
}
template<typename T>
class MyOptional {
static_assert(false == std::is_trivially_copyable_v<T>);
[[no_unique_address]] alignas(T) char unsigned
buf[std::sizeof_minus_trailing_padding<T>()];
bool is_alive = false;
public:
template<typename... Params>
constexpr T &emplace(Params&& ...args) // g++ won't accept
'Params... &&args'
{
if ( this->is_alive ) static_cast<T*>(static_cast<void*>(buf))->~T();
this->is_alive = false;
T &retval = *::new(buf) T( std::forward<Params>(args)... );
this->is_alive = true;
return retval;
}
~MyOptional(void)
{
if ( false == this->is_alive ) return;
static_cast<T*>(static_cast<void*>(buf))->~T();
}
constexpr T& value(void) noexcept { return
*static_cast<T*>(static_cast<void*>(buf)); }
constexpr void abandon(void) noexcept { this->is_alive = false; }
constexpr void reset() noexcept
{
if ( this->is_alive ) static_cast<T*>(static_cast<void*>(buf))->~T();
this->is_alive = false;
}
};
int main(void)
{
MyOptional<A> monkey;
monkey.emplace();
static_assert( sizeof(A) == sizeof(MyOptional<A>) );
}
Received on 2023-11-30 10:18:02