Boost logo

Boost :

Subject: [boost] [contract] Friend functions
From: Lorenzo Caminiti (lorcaminiti_at_[hidden])
Date: 2016-07-19 17:19:54


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


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk