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

5

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!

c++
implicit-conversion
copy-assignment
standard-layout
asked on Stack Overflow Jul 12, 2016 by underscore_d • edited Apr 10, 2018 by Glorfindel

2 Answers

1

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

answered on Stack Overflow Jul 12, 2016 by Cheers and hth. - Alf • edited Jul 12, 2016 by Cheers and hth. - Alf
-1

Compile time type match/mismatch can be controlled by std::enable_if. Implicit type conversion can be disabled by explicit keyword. All of copy and move constructors can be explicitly deleted to avoid copy and default constructor can explicitly marked as default. Edit: early answer takes into account first part of the question that "the user should only be able to pass in or get out the 'exposed' type" so all conversions must be explicit, however to complete the answer you can define a trivially copyable class and use that inside your proxy class May be the thing you intended:

#include  <cstdint>
#include <type_traits>
struct copyable{
    std::uint32_t number = 0x0;
};
class SomeProxyThing {

public:
    explicit operator  std::uint16_t()  const 
    {
        return storage.number & 0x0000FFFF;
    }
template <typename T, typename std::enable_if<std::is_same<T, std::uint16_t>::value, int>::type=0>
    SomeProxyThing& operator=(T value)
    {
        storage.number &= 0xFFFF0000;
        storage.number |= value;
        return *this;
    }

    SomeProxyThing()=default;
    SomeProxyThing(const SomeProxyThing&)=delete;
    SomeProxyThing(SomeProxyThing&&)=delete;
    SomeProxyThing& operator=(const SomeProxyThing& other) {
        this->storage.number = static_cast<std::uint16_t>(other.storage.number);
    }
    SomeProxyThing& operator=(SomeProxyThing&& other) {
        this->storage.number = static_cast<std::uint16_t>(other.storage.number);
    }

private:
    copyable storage;
};
int main()
{
    SomeProxyThing a, b;
    a = static_cast<std::uint16_t>(43);
    b = a; 
}
answered on Stack Overflow Jul 12, 2016 by rahnema1 • edited Jul 12, 2016 by rahnema1

User contributions licensed under CC BY-SA 3.0