To break or not to break… encapsulation

“The devil is in the details” 

A particularity of the C++ data validation concept I wrote about is passing that wrapped_value object as an argument to a function. The reason is for that wrapped value to behave like the type it wraps so that it has a natural usage. I should not know the actual value is hidden by a level of indirection, making it easy to control any mutation.

To achieve that feature, I have used the user-defined conversion function and it went smooth. I can have a wrapped value and pass it as an argument (by value or const reference) to a function:

int inc_by_value(int v) { return v + 1; }
int inc_by_const_ref(const int& a) { return a + 1; }

msd::wrapped_value<int, UpperBoundLimiter<int, 10>> value = 2;
assert(inc_by_value(value) == 3);
assert(inc_by_const_ref(value) == 3);

Pass by non-const reference

The user-defined conversion I initially implemented is the const reference overload:

operator const Value&() const noexcept { return value_; }

That’s why I can pass the wrapped value by value and const reference. To pass it as a non-const reference, I have to implement the non-const reference overload:

template <typename Value, typename... Wrappers>
class wrapped_value {
    // ...
    
    operator Value&() noexcept { return value_; }

    // ...
}

And I can pass by reference and mutate the value:

void inc_by_ref(int& a) { ++a; }

msd::wrapped_value<int, UpperBoundLimiter<int, 10>> value = 2;
inc_by_ref(value);
assert(value == 3);

or

void update(int& a) { a = 20; }

update(value);
assert(value == 10); // should be 10 because of the UpperBoundLimiter

But the last assertion fails: Assertion `value == 10′ failed. This is because the update function has direct access to the value that is being wrapped. It can change it directly, without the wrappers being called. This beats the main purpose of controlling what value can be assigned. It breaks the encapsulation.

Solution

I don’t have a technical solution for this. I want to pass by reference of the inner type, but I don’t want to be able to change it. The two requirements appear to be in conflict. I need more time to approach this situation. At this point, I don’t even know if it’s possible. On the other hand, it’s C++… there might be a more or less orthodox way to manipulate the memory. Any advice would be great.

A compromise could be a precondition for the usage of the wrapped value. I can break the encapsulation and offer the possibility of passing by reference, but anyone using this service should not change the value when passed to a function by reference. Except this leaves an opportunity for a bug that could difficult to find. If you forget that it’s a wrapped value and just look at the function’s parameter, it’s really difficult to guess what goes wrong.

Otherwise, If I keep the encapsulation, it’s safer but more limited.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.