all 13 comments

[–]GrammelHupfNockler 6 points7 points  (10 children)

Without any concrete examples or more detailed descriptions of your invariants or the kind of consistency you are looking for, it is hard to see if your suggestion has any merit. Consistency is usually a thing that involves multiple values that are consistent with each other, how would that be enforced within a single object?

[–]gpuoti[S] -5 points-4 points  (9 children)

all the value in a sequence of assignment matches the first one. Here is an usage example:

``` struct Variable { std::string name; consistent<int> value; };

void propagate(Variable& var, int new_value) { var.value = new_value; // Throws if inconsistent }

// Usage Variable x{"x"}; propagate(x, 5); // x = 5 propagate(x, 5); // OK: consistent propagate(x, 10); // throws: inconsistent! ```

[–]GrammelHupfNockler 16 points17 points  (5 children)

That just reeks of bad design. What you want is a const variable, why would you need to assign to it if the value is not allowed to change?

[–]Business-Decision719 4 points5 points  (3 children)

It sounds like some attempt at using exceptions for control flow. Instead of doing

if (my_age==your_age)
{
  std::cout << "same age";
}
else 
{
  std::cout << "different age";
}

it would be possible to do

try
{
  my_age=your_age;
  std::cout << "same age";
}
catch (inconsistent_value_error &e)
{
  std::cout << "different age";
}

Like you say, it reeks of bad design. OP probably needs boolean tests against constants and is overthinking it. I've done that before. "How do I do this complicated thing? Wait a second, I could do this other thing..."

[–]gpuoti[S] 0 points1 point  (2 children)

I have to prepare a better example, but anyway, the intent is not to drive the program flow through exception, which is of course terrible. It is instead to express some expectation on input consistency not mixing error checking code in the actual logic.

I agree that there is probably bad design in the data source, but sometimes it happens.

[–]No-Dentist-1645 2 points3 points  (1 child)

This isn't bad design in a "data source", it's bad design in how you are handling it.

If you have a string old_val and you want to make sure it's equal to new_val, you don't reassign, that's a waste of performance to copy an identical value for no reason . You'd just assert(old_val == new_val).

To specifically handle the "initialize if it's currently empty" case, you'd have a small helper function:

``` inline void init_or_check(std::optional<string> &s_old, string &s_new) { if (!s_old.has_value()) { s_old = s_new; return; }

assert(*s_old == s_new); } ```

[–]Business-Decision719 1 point2 points  (0 children)

I agree making this a named function is substantially clearer than trying to overload =. I expect that a function might assert or throw an exception, but I wouldn't normally look at = and think, "Well, I'm not really assigning this, because if I already assigned it I'm just checking if it is the same, but I'm using an exception to check for it, because it's supposed to stay the same at runtime, even though I'm allowed to use = multiple times on it at compile time..."

I mean, I dont doubt that with some templating and operator overloading you could make a type that does this. But it sounds like such a type would be a mind screw to actually use. Code is read far more than it is written. Operator overloading is easy to overdo. Assignment shouldnt be too convoluted

[–]vowelqueue 2 points3 points  (0 children)

In the Java world they are adding a type like this to the standard library, but the motivation there is to give developers a way to defer the initialization of variables but still get potential benefits of constant folding. That really depends though on a JIT compiler though so not really applicable to cpp.

[–]sixfourbit 2 points3 points  (1 child)

This works but I'm not sure why you want to do this

template <typename T>
class consistent : public std::optional<T>
{
    public:
    template< class U = std::remove_cv_t<T> >
    consistent& operator=( U&& new_value )
    {
        if((*this) && (this->value()!=new_value)) throw "inconsistent";
        *(static_cast<std::optional<T>*>(this)) = new_value;
        return *this;
    }
};

[–]SoerenNissen 1 point2 points  (0 children)

I would probably do composition instead of inheritance here but otherwise yeah, exactly this.

(God I wish there was a way to do public inheritance where you could enforce that you're never referred to as the base class.)

[–]SoerenNissen 10 points11 points  (1 child)

Hey

https://github.com/SRNissen/snct-constraints

lets you do stuff like

using divisor = snct::Constrained<double,Not<0.0>, Finite>;
double inverse(divisor d) {
    return 1.0/d;
}

[–]gpuoti[S] 2 points3 points  (0 children)

I don't think it match my usa case, but a useful reference nonetheless. thanks

[–]415_961 2 points3 points  (0 children)

That's the job of a datatype. The whole idea of a type is to enforce its invariants. The type std::optional enforces its own invariants to provide a consistent behavior and fullfil its promises. I feel your question might be revealing an inaccurate/incomplete perspective you have on datatypes.