I've found a few threads that heavily imply this can't be done, but none use exactly the same combination of operators and conditions, so I'd like to ask more specifically. Hopefully that means it's a quick and easy answer for someone... one way or another!
Consider an example proxy class, made to manage a value within a larger block of storage - as in this oversimplified but representative example:
class SomeProxyThing {
std::uint32_t storage;
public:
operator std::uint16_t() const
{
return storage & 0x0000FFFF;
}
SomeProxyThing &operator=(std::uint16_t const value)
{
storage &= 0xFFFF0000;
storage |= value;
}
};
I want all assignments to work via the user-defined operator
s. The user should only be able to pass in or get out the 'exposed' type, in this case std::uint16_t
. I might be using various proxy class types and want this to apply to all of them. Ideally, for any combination of types, I could just type someProxy = anotherProxy
and let the compiler do the rest.
But when the left- and right-hand-side of the assignment have the same or inheritance-related types, the default copy assignment operator - of course - conflicts with this goal. It copies the entire storage
, thus clobbering the other half of that uint32_t
- rather than copying just the 'exposed' value as desired. And rightly so! For most cases. But I'd like a way to 'assign by conversion' even if LHS and RHS types are the same. To avoid this, I can:
operator
s - which is what I've been doing, but it seems kinda hacky and, like any user-defined constructor/assignment operator, breaks the trivially copyable status of the struct
- which I need to keep. It still memcpy()
s anyway in g++
, but I want defined behaviour.= delete
the copy-assignment operator (which we can now do for TC types). But assignments still try to use it and throw a compile error - since delete
means 'abort with an error if I'm the chosen overload', not 'exclude me from overload resolution'. To get around this, I must explicitly tell the compiler to use the conversion operator and assign from its result:SomeProxyThing a, b;
a = 42;
b = static_cast<std::uint16_t>(a);
// a.k.a.
b.operator=( a.operator std::uint16_t() );
There doesn't seem to be a way to tell the compiler 'ignore any error generated by your preferred overload and pick the next best one'. Is there? More generally, is there any way/hack/horrifying kludge, in such a situation, to force the compiler to automatically use/prefer certain operator
s?
In other words, ideally, in
SomeProxyThing a, b;
a = 42;
b = a;
that b = a;
would really do this:
b = static_cast<std::uint16_t>(a);
// a.k.a.
b.operator=( a.operator std::uint16_t() );
without me having to type this manually, use a static_cast
, or implement named get/set methods. Ideally, I want reads/writes to any such proxy to look exactly like reads/writes to basic types in written code, all using =
.
I strongly suspect that's not possible... but confirmation would be nice!
You can do this:
#include <stdint.h>
#include <iostream>
#include <type_traits>
using namespace std;
class Proxy_state
{
protected:
uint32_t storage;
public:
// Access to the bytes
};
static_assert( is_trivially_copyable<Proxy_state>::value, "!" );
class Some_proxy_thing
: public Proxy_state
{
private:
public:
operator std::uint16_t() const
{
return storage & 0x0000FFFF;
}
auto operator=( uint16_t const value )
-> Some_proxy_thing&
{
clog << "=(uint16_t)" << endl;
storage &= 0xFFFF0000;
storage |= value;
return *this;
}
auto operator=( Some_proxy_thing const& value )
-> Some_proxy_thing&
{ return operator=( static_cast<uint16_t>( value ) ); }
};
static_assert( not is_trivially_copyable<Some_proxy_thing>::value, "!" );
auto main()
-> int
{
Some_proxy_thing a{};
Some_proxy_thing b{};
const Some_proxy_thing c = b;
a = c;
a = 123;
a = b;
}
Here all three assignments output (to the standard error stream) =(uint16t)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With