Boost logo

Boost :

Subject: Re: [boost] [contract] syntax redesign
From: Lorenzo Caminiti (lorcaminiti_at_[hidden])
Date: 2011-11-09 06:16:17


On Tue, Nov 8, 2011 at 10:35 PM, Dave Abrahams <dave_at_[hidden]> wrote:
>
> on Tue Nov 08 2011, Lorenzo Caminiti <lorcaminiti-AT-gmail.com> wrote:
>
>>> I presume this is essentially "typename Value=int".  I don't love
>>> separating one argument with a comma that way.  Would
>>>
>>>  in typename (Value, int)
>>> or something similar be practical?
>>
>> No because my syntax also supports non-variadic compilers
>
> Oh!
>
>> and the above will have to become (Value)(int) which forces extra
>> parenthesis around (Value) even when there is no default. `...,
>> default ...` is consistently used to indicate default parameter value
>> (template or functional) and in my experience you get used to it
>> pretty quickly.
>
> I don't mind "default" at all, but the fact that it is separated by a
> comma from "in typename Value", with no grouping to distinguish that
> comma from the commas that separate parameters, is ugly.  If it's the
> only way to make it work, then so be it, but I'd prefer to have the
> default value grouped somehow with the parameter name.

The following can be done:

#define WITH_DEFAULT , default

template( typename T, typename U WITH_DEFAULT int ) // (1)

Anyway, I can see why (1) seems more natural at first but after using
the syntax my experience really is that `typename U, default int` is
just as good (actually better because less cryptic). (For non variadic
compilers `#define WITH_DEFAULT )(default`.)

>> In fact, I personally prefer Value, default int to (Value, int)
>> (because in the second there is no description of the semantic of the
>> 2nd token int).
>>
>>>>     in requires(is_same<boost::mpl::_::type, Value>) Default,
>>>>             default boost::mpl::int_<1>
>>>
>>>
>>> I don't understand what this is supposed to be saying.  Could you
>>> explain it?
>>
>> This is a way of passing a named value template parameter by wrapping
>> within a type template parameter as you suggested...
>
> Well, it may be, but I don't understand how it works.

In "normal" C++, I want to do:

typename<
      typename Value = int
    , Value default_value = 1
>

But I can only use type template parameters (because of
Boost.Parameter limitations) so I do:

typename<
      typename Value = int
    , typename Default = mpl::int_<1>
>

With the requirement (not yet programmed) that Default::type == Value.
In Boost.Contract syntax, it becomes:

typename(
      in typename Value, default int
    , in requires(is_same<typename mpl::_::type, Value>) Default,
default mpl::int_<1> // (2)
)

>> is there a better way to do this? I want to pass:
>>
>> template< typename Value, Value default_value = 1 >
>>
>> Where both Value and default_value are named using Boost.Parameter.
>
> If I were you I would try to think a little beyond the limitations of
> Boost.Parameter.  If there's an interface we can implement that better
> supports this use-case, we'll add it.  Is there?

OK. The Boost.Contract syntax itself can do this following the same
conventions we discussed for function parameters. In "normal" C++":

template<
      typename Value = int
    , Value default_value = 1
>

In an hypothetical C++ with named parameters, it'd become (just prefix
the parameters with `in`):

template<
      in typename Value = int
    , in Value default_value = 1
>

Now in Boost.Contract syntax, it becomes (replace <> with (), wrap
user and qualified types within extra parenthesis, and use default
instead of =):

template(
      in typename Value, default int
    , in (Value) default_value, default 1
)

The syntax itself would support this representation of value template
parameters however then I cannot use Boost.Parameter behind the scenes
to implement it. Therefore, with the workaround of using the int_ type
instead of the int value for the default-value template parameter, it
becomes:

typename(
      in typename Value, default int
    , in requires(is_same<typename mpl::_::type, Value>) Default,
default mpl::int_<1>
)

>>> Have you thought of using decltype in the implementation, to avoid
>>> writing ::type up there?
>>
>> I'm not using C++1 features. (The C++11-looking auto, static_assert,
>> etc that you see in Boost.Contract are all handled by the pp and
>> implemented using C++03.)
>
> Wow!  But decltype might be (a little) different, since we can emulate
> it with Boost.Typeof.  Just something to consider.

Yes, I do use Boost.Typeof (that's how auto is implemented by the
macro expansion after being parsed by the pp).

>>> Something like boost::mpl::_::type seems like maybe it should be
>>> spelled "auto" (if possible).
>>
>> It cannot because it is nested inside the named parameter type
>> predicate so the pp has to way to parse it.
>
> Right.  Well, boost::contract::auto_ then?  I know, yuck :-P

I'm not sure. The point is that mpl::_ is an int_, char_, long_, etc
in this case and I want to express the constraint int_::type == Value,
char_::type == Value, etc as explained in (2) above. Therefore, I
think the use of mpl::_::type is consistent with using unary
metafunctions to express named parameter type requirements.

Note that I just learned yesterday that type expressions need all to
be evaluated lazily with Boost.Parameter type requirements so ::type
will not actually work here. I will have to go via another
metafunction like:

template< typename F, typename T >
struct is_same_type : is_same< typename F::type, T > {};

typename(
      in typename Value, default int
    , in requires(is_same_type<mpl::_, Value>) Default, default mpl::int_<1>
)

>> ::type is needed because I am using a type to wrap a value so the
>> value template parameter can be named (as indicated above).
>
> I don't understand what you're saying here yet.

Was I able to clarify this with the examples above?

>>>> ) requires( boost::Copyable<Value> ) // Concepts.
>>>> struct (positive)
>>>> ) {
>>>>     CONTRACT_CLASS_INVARIANT_TPL( // Contracts.
>
> Because it's an invariant for a class template, not an invariant
> template.  At least, IIUC.
>
>>> Why is that a better spelling than CONTRACT_CLASS_TPL_INVARIANT?

Sorry I forgot to address this. The _TPL postfix is added to the
contract macros when they are used within a template. So all
CONTRACT_CLASS_INVARIANT, CONTRACT_FUNCTION, CONTRACT_CONSTRUCTOR, etc
become CONTRACT_CLASS_INVARIANT_TPL, CONTRACT_FUNCTION_TPL,
CONTRACT_CONSTRUCTOR_TPL, etc when the enclosing class is a template.

Rationale. This is needed because C++03 doesn't allow to use typename
freely outside templates :( There is actually a mechanism that I can
use to not require the _TPL postfix but it relies on making /all/
functions generated by the contract macros function templates and
therefore it increases compilation time so I preferred to ask the user
to say when the enclosing scope is a template by specifying the _TPL
postfix.

>>>>         static class(
>>>>             static_assert(Default::value > 0, "positive default value")
>>>>         ),
>>>
>>> I don't understand what the "static class(...)" construct is doing here.
>>
>> This will be detailed in Boost.Contract docs (it's an addition over
>> N1962). All assertions within static class(...) specify /static class
>> invariants/. (Non-static) class invariants are not checked at
>> entry/exit of static functions or at constructor entry. However,
>> static class invariants are also checked at entry/exit of static
>> functions and at constructor entry.
>
> Hm, OK.
>
>> Obviously, static class invaraints cannot refer to the object (but
>> only to static member data/functions).  In addition, this one static
>> class invariant is using a static assertion (for this example, given
>> that the assertion is static it could also have been programmed as
>> part of the non-static class invariants and get the same type of
>> checking which is always at compile-time for static_assert).
>
> [OK. It would be good to have an example in the end product that doesn't
> include that kind of redundancy.]
>
>> Static class invariants were discussed with N1962 authors 1+ years ago
>> over the Boost ML (no one had objections on them but no one seemed to
>> think they will be useful in real life).
>
> In that case, I think you should omit them.  Premature generalization is
> the root of all evil ;-)

I'd like to leave them. I tough about it for a while and I want to be
bold in suggesting both static and volatile class invariants because I
think they make sense for Contract Programming in C++. I will of
course have to document my reasons for the "addition" to these feature
well. The main point is that contracts should cover and integrate with
/all/ C++ features (which are many and diverse). Static and volatile
class invariants are an example of this. Another example are assertion
requirements (also not part of N1962) that can be used to handle the
outstanding case of types with a copy constructor but without an
operator==. The generalization principle here is not to add "extra"
features for contracts, it is instead for contracts to cover all
existing C++ features (static and volatile members included).

>>>>         get() > 0 )
>>>>
>>>>     CONTRACT_CONSTRUCTOR_TPL(
>>>>     public (positive) ( void )
>>>
>>> Is "void" mandatory here?
>>
>> No if your compiler supports empty macro parameters or variadics.
>> However, often MSVC (which in theory supports both) gets confused and
>> generates pp-error. So I always use void to indicate and empty
>> (parameter) list so the code is most portable.
>
> Ah.
>
>>>>         initialize( value_(Default::value) ) ) {}
>>>
>>> What is "value_"?
>>
>> A private member variable.
>
> OK, thanks.
>
>>>>     CONTRACT_FUNCTION_TPL(
>>>>     public void (set) ( namespace keywords, in (Value const&) value )
>>>>         precondition( value > 0 )
>>>>         postcondition(
>>>>             get() == value, requires boost::has_equal_to<Value>::value
>>>
>>> Why must ::value appear in the previous line?
>>
>> Because assertion requirements are specified using integral static
>> constants and not nullary metafunctions. This could be (esaily)
>> changed to be more consistent with named parameter requires that uses
>> unary metafunctions... I'll think about it.
>
> Please do.

Why would you prefer the assertion requirement to be a nullary
metafunction instead than a integral static constant? One reason now
is because named parameter type requirements are (unary) metafunctions
but I am sincerely asking if there are other good reasons for this.

>>> This is very nice, but still could use some explanation and
>>> simplification.
>>
>> Explanation will definitely go in the docs. What simplifications would
>> you suggest?
>
> E.g. leaving out the "namespace" stuff and the features that nobody
> thinks will be useful.

OK, let me try again:

#include <contract.hpp>
#include <boost/concept_check.hpp>
#include <boost/type_traits/has_equal_to.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/mpl/placeholders.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/char.hpp>
#include <boost/mpl/long.hpp>
#include <iostream>

namespace num {

template< typename F, typename T >
struct is_same_type : boost::is_same<typename F::type, T> {};

CONTRACT_TEMPLATE_PARAMETER(Value)
CONTRACT_TEMPLATE_PARAMETER(Default)
CONTRACT_PARAMETER(value)

CONTRACT_CLASS(
template( // Named parameters.
    in typename Value, default int,
    in requires(is_same_type<boost::mpl::_, Value>) Default,
            default boost::mpl::int_<1>
) requires( boost::Copyable<Value> ) // Concepts.
struct (positive)
) {
    CONTRACT_CLASS_INVARIANT_TPL( // Contracts.
        static_assert(Default::value > 0, "positive default value"),
        get() > 0
    )

    CONTRACT_CONSTRUCTOR_TPL(
    public (positive) ( void )
        initialize( value_(Default::value) )
    ) {}

    CONTRACT_DESTRUCTOR_TPL(
    public (~positive) ( void )
    ) {}

    CONTRACT_FUNCTION_TPL(
    public void (set) ( in (Value const&) value ) // Named parameter.
        precondition( value > 0 )
        postcondition(
            get() == value, requires boost::has_equal_to<Value>::value
        )
    ) {
        value_ = value;
    }

    CONTRACT_FUNCTION_TPL(
    public (Value const&) (get) ( void ) const
    ) {
        return value_;
    }

private:
    Value value_;
};

} // namespace num

int main ( void )
{
    num::positive< char, boost::mpl::char_<'z'> > p;
    num::positive< > q;

    num::positive< num::_Default< boost::mpl::int_<10> > > r;
    num::positive<
          num::_Default< boost::mpl::long_<10> >
        , num::_Value<long>
> s;

    std::cout << s.get() << std::endl;
    s.set(num::_value = 123);
    std::cout << s.get() << std::endl;

    return 0;
}

>> The goal here is to use Boost.Contract to /completely/ specify the
>> interface for the positive abstract data type.

Thanks!
--Lorenzo


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