Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to make `=` prefer assignment-from-conversion over (deleted) copy-assignment?

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 operators. 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:

  • redefine the copy assignment operator to perform a 'proxied' copy using the user-defined operators - 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.
  • or = 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 operators?

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!

like image 511
underscore_d Avatar asked Nov 20 '22 00:11

underscore_d


1 Answers

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).

like image 135
Cheers and hth. - Alf Avatar answered Dec 19 '22 06:12

Cheers and hth. - Alf