Date: Tue, 12 Dec 2023 01:25:19 +0000
On Mon, Dec 11, 2023 at 8:35 PM Arthur O'Dwyer wrote:
>
> Thanks :) but I don't think `is_trivially_relocatable` is useful here. You never relocate-out-of (nor into) a `T` here. And the requirements on `T` are much stricter than "trivially relocatable"; for example, you actually need it to be trivially destructible, or else `unaligned<T>` won't have any destructor.
> For example: I don't think it makes sense to talk about `std::unaligned<std::string>`. I don't know what the semantics of that would be.
You've done a lot more work on trivial relocatability than I have, but
anyway let me try tease this out.
Let's say I make a new string class, something like
std::static_string, and it looks something like:
struct static_string {
char buf[256u];
};
Of course, such a class would be trivially relocatable. But let's say
now that I allow the null character to be just another character in
the string, so now I need an extra member to indicate how long the
string is:
struct static_string {
char buf[256u];
unsigned len;
};
With this change, the class is still trivially relocatable. But let's
throw a spanner in the works. Instead of having a member named 'len',
we instead store a pointer to the last character, as follows:
struct static_string {
char buf[256u];
char *plast;
};
And so if the string is 10 characters long, then 'plast' will be equal
to 'this + 10 - 1'. The class is no longer trivially relocatable.
Arthur you said in your last post that I "never relocate-out-of (nor
into) a `T`", but if you look at my implementation of the assignment
operator (which I implemented as a template function), I do a memcpy
to a suitably-aligned T-sized buffer and then perform an operation on
it -- it is for this reason that I need T to be trivially relocatable.
You also mentioned that it must have a trivial destructor but I think
this matter is covered by Connor's use of:
~unaligned() requires std::is_trivially_destructible_v<T> = default;
...because if I do the following:
class cannot_destroy { ~cannot_destroy(){} };
unaligned<cannot_destroy> monkey;
then I get the following compiler error:
<source>:56:31: error: use of deleted function
'unaligned<T>::unaligned() requires
!(is_trivially_default_constructible_v<T>) [with T = cannot_destroy]'
56 | unaligned<cannot_destroy> uncd;
So maybe this compiler error is good enough?
Anyway I've added a load of binary operators and unary operators to my
previous code (adapted from Connor's code), and here's what I've got
now:
https://godbolt.org/z/z7MGMnzre
But even after we put together a robust implementation of
std::unaligned (and I think we're getting close to it), I still want
to be able to do:
struct TokenInfo {
long unsigned a;
char b;
long unsigned c;
char d;
};
typedef struct TokenInfo [[packed]] TicketInfo;
and have the compiler treat it as though I had written:
struct TokenInfo {
long unsigned a;
char b;
long unsigned c;
char d;
};
struct TicketInfo {
unaligned<long unsigned> a;
unaligned<char> b;
unaligned<long unsigned> c;
unaligned<char> d;
};
Furthermore, I think that 'unaligned' should be compatible with
'optional' so that you can do the following:
std::optional<SomeClass> monkey;
monkey.emplace(1,2,3,56.7L);
std::unaligned<SomeClass> donkey( std::take_ownership_t(), monkey );
After these three lines of code, 'monkey' no longer has a value, and
so the 'donkey' object is now responsible for destroying the SomeClass
object (and in this case I would not require T to be trivially
destructible). Therefore the destructor of 'unaligned' would have to
do something like:
~unaligned(void)
{
alignas(T) std::byte buf[ __datasizeof(T) ];
T &tmp = *static_cast<T*>(static_cast<void*>(buf));
std::memcpy(&tmp, &_m_data.front(), __datasizeof(T));
tmp.~T();
}
Also another thing... I would want the 'unaligned' class to be derived
from a base class such as:
class unaligned_base {};
class unaligned : public unaligned_base {};
and for as much of the implementation as possible to be put inside
'unaligned_base', so that in the future we could make another type
called 'unaligned_ref' (derived from 'unaligned_base'), which takes
control of the buffer you supply to it in its constructor.
>
> Thanks :) but I don't think `is_trivially_relocatable` is useful here. You never relocate-out-of (nor into) a `T` here. And the requirements on `T` are much stricter than "trivially relocatable"; for example, you actually need it to be trivially destructible, or else `unaligned<T>` won't have any destructor.
> For example: I don't think it makes sense to talk about `std::unaligned<std::string>`. I don't know what the semantics of that would be.
You've done a lot more work on trivial relocatability than I have, but
anyway let me try tease this out.
Let's say I make a new string class, something like
std::static_string, and it looks something like:
struct static_string {
char buf[256u];
};
Of course, such a class would be trivially relocatable. But let's say
now that I allow the null character to be just another character in
the string, so now I need an extra member to indicate how long the
string is:
struct static_string {
char buf[256u];
unsigned len;
};
With this change, the class is still trivially relocatable. But let's
throw a spanner in the works. Instead of having a member named 'len',
we instead store a pointer to the last character, as follows:
struct static_string {
char buf[256u];
char *plast;
};
And so if the string is 10 characters long, then 'plast' will be equal
to 'this + 10 - 1'. The class is no longer trivially relocatable.
Arthur you said in your last post that I "never relocate-out-of (nor
into) a `T`", but if you look at my implementation of the assignment
operator (which I implemented as a template function), I do a memcpy
to a suitably-aligned T-sized buffer and then perform an operation on
it -- it is for this reason that I need T to be trivially relocatable.
You also mentioned that it must have a trivial destructor but I think
this matter is covered by Connor's use of:
~unaligned() requires std::is_trivially_destructible_v<T> = default;
...because if I do the following:
class cannot_destroy { ~cannot_destroy(){} };
unaligned<cannot_destroy> monkey;
then I get the following compiler error:
<source>:56:31: error: use of deleted function
'unaligned<T>::unaligned() requires
!(is_trivially_default_constructible_v<T>) [with T = cannot_destroy]'
56 | unaligned<cannot_destroy> uncd;
So maybe this compiler error is good enough?
Anyway I've added a load of binary operators and unary operators to my
previous code (adapted from Connor's code), and here's what I've got
now:
https://godbolt.org/z/z7MGMnzre
But even after we put together a robust implementation of
std::unaligned (and I think we're getting close to it), I still want
to be able to do:
struct TokenInfo {
long unsigned a;
char b;
long unsigned c;
char d;
};
typedef struct TokenInfo [[packed]] TicketInfo;
and have the compiler treat it as though I had written:
struct TokenInfo {
long unsigned a;
char b;
long unsigned c;
char d;
};
struct TicketInfo {
unaligned<long unsigned> a;
unaligned<char> b;
unaligned<long unsigned> c;
unaligned<char> d;
};
Furthermore, I think that 'unaligned' should be compatible with
'optional' so that you can do the following:
std::optional<SomeClass> monkey;
monkey.emplace(1,2,3,56.7L);
std::unaligned<SomeClass> donkey( std::take_ownership_t(), monkey );
After these three lines of code, 'monkey' no longer has a value, and
so the 'donkey' object is now responsible for destroying the SomeClass
object (and in this case I would not require T to be trivially
destructible). Therefore the destructor of 'unaligned' would have to
do something like:
~unaligned(void)
{
alignas(T) std::byte buf[ __datasizeof(T) ];
T &tmp = *static_cast<T*>(static_cast<void*>(buf));
std::memcpy(&tmp, &_m_data.front(), __datasizeof(T));
tmp.~T();
}
Also another thing... I would want the 'unaligned' class to be derived
from a base class such as:
class unaligned_base {};
class unaligned : public unaligned_base {};
and for as much of the implementation as possible to be put inside
'unaligned_base', so that in the future we could make another type
called 'unaligned_ref' (derived from 'unaligned_base'), which takes
control of the buffer you supply to it in its constructor.
Received on 2023-12-12 01:25:24