Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between equality-preserving and stable?

According to the draft:

An expression is equality-preserving if, given equal inputs, the expression results in equal outputs.[...]

and

[...]stable: two evaluations of such an expression with the same input objects are required to have equal outputs absent any explicit intervening modification of those input objects.[...]

emphasis mine.

  • What's the difference between these?

  • When an expression can be equality-preserving but not stable and vice-versa?

like image 976
Nazinho Avatar asked Sep 26 '19 05:09

Nazinho


2 Answers

The two would be different for an operation that modified its inputs.

// stable and equality preserving
int foo(int a, int b) { 
    return a + b;
}

// equality preserving, but not stable:
int bar(int a, int &b) { 
    auto ret = a + b;
    ++b;
    return ret;
}

For example:

int x = 1, y = 2;
int z = foo(x, y); // produces 3

int z2 = foo(x, y);  // still produces 3

int zz = bar(x, y); // produces 3
int zz2 = bar(x, y); // produces 4

As for something that was stable but not equality preserving, yes, that's also possible (for some definitions of "equal").

For a trivial example, consider something like this:

struct foo { 
    int bar;

    // they're all equal, just some are more equal than others
    bool operator==(foo const &) const { return true; }
};

int operator+(foo a, foo b) { return a.bar + b.bar; }

foo a{1};
foo b{2};
foo c{3};

// obviously both true
assert(a == b);
assert(b == c);

int x = a + b;
int y = b + c;

assert(x != y); // of course 1 + 2 != 2 + 3;
like image 186
Jerry Coffin Avatar answered Sep 26 '22 00:09

Jerry Coffin


The approved answer is incorrect. This function:

int foo(int a, int b) { 
    return a + b;
}

is indeed equality-preserving and stable. But so is this function:

int bar(int a, int &b) { 
    auto ret = a + b;
    ++b;
    return ret;
}

The actual difference between the two is that foo is regular (cf concept regular_invocable) but bar is not.

An example of a function that is equality-preserving but not stable is:

int b = 0;  // assume accessible by other threads
int zip(int a) { 
    auto ret = a + b;
    ++b;
    return ret;
}

It is equality-preserving because equality-preservation is purely static, and at any given time, zip will indeed give the same result with the same input.

However, zip is not stable because stability is dynamic, and at different times, zip may give different results with the same input.

(If the global variable b was unchanged in-between the calls, then it will give a different result. But if another thread decremented b, and no other change occurred, it will give the same result.)

(If no other thread could access the b variable, notwithstanding how this can be achieved practically, we would have stability, because we could observe an "explicit intervening modification". This the reason the standard mentions that stability prohibits "spontaneous" changes, aka volatility, changes from other threads of execution, and so on. Cf concepts.lib.general.equality)

Also note that equality-preservation does not depend on the availability of an equality operator (and the equality_comparable concept actually requires equality preservation). Equality is not explicitly defined, but should be considered as substitutability, or indistinguishability, which is often mentioned by the standard, such as in strong_ordering.

A function cannot be stable without being equality-preserving, since stability is the dynamic extension of the static notion of equality-preservation.

like image 34
Alcen Avatar answered Sep 24 '22 00:09

Alcen