[contract] Friend functions
Hello all, I would like to discuss contracts for friend functions and specifically how they are handled in Boost.Contract. Consider the following example: class a; class b; class data { ... friend bool deep_equal(a*, b*); bool equal(data const&); }; class a { ... friend bool deep_equal(a*, b*); data data_; }; class b { ... friend bool deep_equal(a*, b*); data data_; }; bool deep_equal(a* x, b* y) { return x->data_.equal(y->data_); } Via friendship, deep_equal can be considered to extend a and b public interfaces, but not data's public interface (in fact, deep_equal does not take instance of data as an argument). Therefore, it is reasonable to think that deep_equal should check a and b class invariants (if so... in which order?), but not data's class invariants. As discussed in N4160, that would probably require some new language feature to specify which subset of friend functions also extend a class public interface. Plus such a feature should identify some preferred order in which class invariants should be checked for a friend function that extends public interfaces of multiple classes. That seems rather complicated so I think a language proposal (like N1962) is better off going with the general rule that friends functions do not check class invariants (even if that might not always be ideal in all cases): bool deep_equal(a* x, b* y) [[requires: x && y]] // Does not check a and b (and c) invariants... even if arguably it should. { return x->data_.equal(y->data_); } In Boost.Contract: bool deep_equal(a* x, b* y) { boost::contract::guard c = boost::contract::function() .precondition([&] { BOOST_CONTRACT_ASSERT(x && y); }) ; // Does not check a and b (and c) invariants... even if arguably it should. return x->data_.equal(y->data_); } Maybe programmers could manually check a and b invariants (but then also from the function body when it throws) when those checks are truly essential for friend function: bool deep_equal(a* x, b* x) [[requires: x && y]] // Check a and b invariants in that order (if contract writes decide to), does not check c invariants. [[requires: x->invariant() && y->invariant()] [[ensures: x->invariant() && y->invariant()] { try { return x->data_.equal(y->data_); } catch(...) { [[assert: x->invariant() && y->invariant()]]; throw; } } (That seems somewhat verbose... plus it assumes that class invariants are somehow specified in a `bool invariant() const function (that can be private), or using a set of [[invariant: ...]] attributes that automatically generate such a callable `bool invariant() const` function.) This is possible in Boost.Contract: bool deep_equal(a* x, b* y) { boost::contract::guard c = boost::contract::function() .precondition([&] { BOOST_CONTRACT_ASSERT(x && y); }) ; // Check a and b invariants in that order (if contract writers decide to), does not check c invariants. boost::contract::guard inv_x = boost::contract::public_function(*x); boost::contract::guard inv_y = boost::contract::public_function(*y); return x->data_.equal(y->data_); } Finally, note that in this case preconditions are checked before entry invariants (while usually public functions check preconditions after entry invariants). That is desirable/needed for friend functions because the class object is not *this (which is always well defined and therefore subject to no precondition). For friend functions, the class object can be any computation of the friend function arguments and those arguments must be validated by preconditions before they can be used to access the class object. In the examples above, the arguments are checked to be not null by preconditions so they can be safely dereferenced to get the objects *x and *y (and in general the computations required might be even much more complex than dereferencing pointers). Thank you. --Lorenzo
participants (1)
-
Lorenzo Caminiti