2

My problem is that I have to choose between copying const data, and moving non-const data, and I conceptually do not see a reason why, in my situation, it wouldn't be safe to move const data to a const destination. I am also aware that moving from a const object is illogical - so perhaps there's some other way to achieve what I am attempting to do...

I have a Builder class that sets up a BigData object that I don't want to have to copy around. Once the builder is finished it 'emits' the data as const, because it shouldn't be modified after that point. The builder is temporary, and can then be discarded, but the data should live on, as a const member in a wrapper class that can interrogate it. The builder is a generic type (doesn't know about the details of the data), while the wrapper is specific to the contents of the data. This is achieved via type-erasure within the data, but that's not relevant to this question, except to make it clear that it isn't desirable to merge the builder and wrapper.

The below code describes the above, and does not compile unless you remove BigData's move constructor. The compile error shows that this code would copy the data, which is undesirable:

prog.cpp:26:57: error: use of deleted function ‘constexpr BigData::BigData(const BigData&)’
     Wrapper( const BigData&& In ) : Data( std::move(In) ) {}
                                                         ^
class BigData
{
    // Only a builder can create data
    friend class Builder;
    BigData() = default;
public:
    // Move only
    BigData( BigData&& ) = default;
};

class Builder
{
    BigData Data;
public:
    const BigData&& Build() & { return std::move(Data); }
    // Functions to set up data go here
};

class Wrapper
{
    const BigData Data;
public:
    Wrapper( const BigData&& In ) : Data( std::move(In) ) {}
    
    // Functions to query data go here
};

std::unique_ptr<Wrapper> DoBuild()
{
    Builder b;
    // call functions on b to set up data here
    return std::make_unique<Wrapper>( b.Build() );
}

The above code can also be found at https://ideone.com/tFkVEd.

The problem can be fixed by changing all const BigData&& into just BigData&& but I don't want the data to be changeable after it is retrieved from Build().

Finally, the problem could also be solved by returning the data from Build in a unique_ptr<const BigData>, and have the Wrapper take ownership of this, but my problem with this is that a pointer can be null, so all usage of Data within Wrapper would have to first check that the Data pointer is valid. This is why I want to move the const data into the wrapper via an rvalue reference, and also why the wrapper must be constructed with the data.

Is there any way to achieve what I want, or have I come up against a limitation of c++?

Many thanks in advance for your input.

NB: Related questions that I have encountered while trying to solve this myself:

Malrog
  • 23
  • 5
  • 2
    Moving from an object will modify it. You can't modify a `const` object. – super Nov 18 '20 at 10:01
  • Yep, I realised it was illogical, and was hoping there was some other solution to my problem. After all, the const object isn't used after the move, so it doesn't matter that it was modified. I guess what I'm looking for here is a way to transfer ownership of a const thing without using pointers, and it doesn't appear that this is possible within c++ at present. – Malrog Nov 18 '20 at 10:43
  • 1
    There is. Add a layer of indirection. Wrap the `BigData` in something. I would use a smart pointer. If you are scared of the pointer being null, you can always make some kind of factory method for this that doesn't allow wrapper to have a null pointer. – super Nov 18 '20 at 13:45

2 Answers2

5

There is no way to std::move a const T as it would mutate the given object. (Unless you make the members mutable and make a T(const T&&) constructor to do the move, which I would not recommend).

A better solution in my opinion would be to make all the mutating functions private, such that only the builder can access them, and return a T&&.

Mestkon
  • 3,532
  • 7
  • 18
  • I had considered a `const T&&` constructor but it felt messy! So this was the conclusion I came to as well (private mutators), but I wondered if I had missed anything in the language, or in my class design, to support the desired behaviour, as I'm always wary whenever I have to use `friend`. It seems I have not, and that I'll just have to follow that approach. Thanks for the feedback :) – Malrog Nov 18 '20 at 10:38
1

firstly, to move an object, exactly means to modify it.

secondly, I don't think you should prevent all the modifications to BigData. trying to return a BigData const&& and prevent it to be modified, is just like trying to prevent it to be const_cast-ed, which is a wasted effort. in this case, you can just declare that to modify a BigData inside a Builder is definitely an undefined behavior, except to move it and then return a BigData&&.

thirdly, if you want it not to be modified exactly (except to be moved), you should consider if to construct a Wrapper is the only usage for Builder. if so, why not provide a constructer Wrapper(Build&) to do that? it can prevent all potential modifications from Builder to Wrapper.

finally, to answer the question how to move a const BigData object, I suggest BigData(BigData const&&) and const_cast, which means you allow all const BigData to be moved as if it was non-const.

RedFog
  • 1,005
  • 4
  • 10
  • I like the idea of constructing a Wrapper from a Builder. I will consider this. Thank you. – Malrog Nov 18 '20 at 12:03
  • 1
    ...with the usual caveat that if the `BigData const&&`, which you then `const_cast` to non-`const`, was originally declared `const` - your program has undefined behaviour. – underscore_d Nov 18 '20 at 12:48
  • @underscore_d yes, I never think `BigData(BigData const&&)` should exist. but if necessary, I suggest providing it and assuming that all `const BigData` are actually non-const. – RedFog Nov 19 '20 at 01:48