Sorry for the many code examples in this reply... but I think they
illustrate the topics better than my English ;)
On Fri, Jun 24, 2016 at 3:49 PM, Lorenzo Caminiti
On Fri, Jun 24, 2016 at 2:43 PM, Andrzej Krzemienski
wrote: 2016-06-15 17:30 GMT+02:00 Lorenzo Caminiti
: Preconditions are evaluated after local object's destructors.
Andrzej, thanks for clarifying (to me in a separate email) that this
means "postconditions should be evaluated after local object
destruction". That is indeed what Boost.Contract does and it is
necessary because in C++ non-trivial destructors can actively
participate to establishing postconditions (using RAII, etc.). For
example:
void fswap(file& x, file& y) // (A)
[[requires: x.closed() && y.closed()]]
[[ensures: x.closed() && y.closed()]]
[[ensures: x == oldof(y) && y == oldof(x)]]
{
x.open();
scope_exit close_x([&x] { x.close(); });
y.open();
scope_exit close_y([&y] { y.close(); });
file t = file::temp();
t.open();
scope_exit close_t([&t] { t.close(); });
x.mv(t);
y.mv(x);
t.mv(y);
}
That is also why P0380 should add old value support... that is because
the above cannot be emulated using [[assert]] like suggested in P0380
at page 10:
void fswap(file& x, file& y)
[[requires: x.closed() && y.closed()]]
{
file old_x = x;
file old_y = y;
x.open();
scope_exit close_x([&x] { x.close(); });
y.open();
scope_exit close_y([&y] { y.close(); });
file t = file::temp();
t.open();
scope_exit close_t([&t] { t.close(); });
x.mv(t);
y.mv(x);
t.mv(y);
[[assert: x.closed() && y.closed()]] // Fails! (Established by
local dtors).
[[assert: x == old_y && y == old_x]]
}
To make old value emulation work here [[assert]] would need to also be
programmed in a scope_exit:
void fswap(file& x, file& y)
[[requires: x.closed() && y.closed()]]
{
file old_x = x;
file old_y = y;
scope_exit ensures([&] {
[[assert: x.closed() && y.closed()]]
[[assert: x == old_y && y == old_x]]
});
x.open();
scope_exit close_x([&x] { x.close(); });
y.open();
scope_exit close_y([&y] { y.close(); });
file t = file::temp();
t.open();
scope_exit close_t([&t] { t.close(); });
x.mv(t);
y.mv(x);
t.mv(y);
}
Now that's a lot of boiler-plate code to program manually for a
language that has native support for an [[ensure]] statement. I really
would expect to be able to do something like (A) above in a language
that supports [[ensures]].
Of course, when you introduce old values then there is the question of
what to do in template code when the generic type of an old value
expression might be copy constructible some times, but not all the
times. Boost.Contract allows programmers to use a type
old_ptr_noncopyable<T> that will copy the old value in the pointer if
T is copy constructible, otherwise it will leave the pointer null
(without generating a compiler error). Then programmers can assert
postconditions that use that old value only if the related pointer is
not null. For example:
template<typename T>
void accumulate(T& total, T const& x) {
// No compiler error if T has no copy constructor...
boost::contract::old_ptr_noncopyable<T> old_total =
BOOST_CONTRACT_OLDOF(total);
boost::contract::guard c = boost::contract::function()
.postcondition([&] {
// ...but old value null if T has no copy constructor.
if(old_total) BOOST_CONTRACT_ASSERT(total == *old_total + x);
})
;
total += x;
}
https://lcaminiti.github.io/boost-contract/doc/html/boost_contract/advanced_...
If programmers decide to use old_ptr<T> instead then Boost.Contract
will generate a compiler error if T is not copy constructible (and
that might be a fare way to program contracts as well in some cases,
it's up to the programmers).
With language support something similar could be done if some sort of
static-if could be used within contract assertions (this is not a full
blown static-if which was rejected already for C++... it'd just be a
static-if to be used in contract assertions):
template<typename T>
void accumulate(T& total, T const& x)
[[ensures: static if(is_copyable<T>::value) total == oldof(total) + x]]
{
total += x;
}
Otherwise, if static-if could not be added to contracts (because too
controversial, etc.), assuming Boost.Contract call_if/check_if
facility and generic lambdas can be used from contract assertions the
following could be done (a bit more verbose then the static-if
version):
template<typename T>
void accumulate(T& total, T const& x)
[[ensures:
check_if