[contract] static_assert in contracts or not?

Hello all, If static assertions make sense within contracts (preconditions, postconditions, and class invariants) is a questions that has been raised by both Vicente and Andrzej. I don't have a definitive answer so I'd like to discuss with the ML. On Wed, Aug 29, 2012 at 2:48 PM, Andrzej Krzemienski <akrzemi1@gmail.com> wrote:
(6) Static assertions: is there any value in providing them, given that we already have this ability in Boost? I see that N1962 decided to abandon them; I am pretty sure the reason was that static_assert was already there. They somehow do not fit conceptually into the model of preconditions, postconditions and invariants. Precondition, for instance tries to detect a run-time condition occurring immediately before a function is called this time. Static assertion is not even executed then. What point is there in putting such assertion into the precondition? Documentation says " static assertions can be selectively disabled depending on where they are specified [...] so it is still important to logically associate them with preconditions, postconditions, etc." -- technically this sentence makes logical sense, but can you think of a single example where this selective disabling of assertions would be useful? What is the point in disabling such assertions at all, if they cost you nothing in run-time? I propose to remove them and give a sort of recommendation that software quality is assured by using a number of fairly independent tools: DbC, concepts, static assertions.
I think we can break down the question in two parts. Let's assume N1962 is accepted into C++1x so we have both contracts and static_assert. 1) Would you expect to be able to use static_assert within contract assertions (preconditions, postconditions, and class invariants)? Note that only boolean conditions are allowed by N1962 within contracts, general code is not allowed, therefore this is not a trivial question. For example, would you expect to be able to do the following option A: template< typename To, typename From > To* memcopy ( To* to, From* from ) precondition{ static_assert(sizeof(To) >= sizeof(From), "destination too small"); static_assert(boost::is_convertible<From, To>::value, "incompatible types"); to; // pointer not null from; // pointer not null } { // ... } In this case, would it ever make sense to use static_assert in postcondition and/or class invariants even if that were allowed? Or would you just use static_assert in the body, option B: template< typename To, typename From > To* memcopy ( To* to, From* from ) precondition{ to; // pointer not null from; // pointer not null } { static_assert(sizeof(To) >= sizeof(From), "destination too small"); static_assert(boost::is_convertible<From, To>::value, "incompatible types"); // ... } I don't like this because the assertions are about the specifications (they assert a requirement of the interface) so they should go in the declaration and not in the implementation. Or maybe the assertions should be represented as concept requirements on To and From, option C: template< typename To, typename From > requires SizeofGreaterEqual<To, From>, Convertible<From, To> To* memcopy ( To* to, From* from ) precondition{ to; // pointer not null from; // pointer not null } { // ... } (Only we don't have concepts...) 2) Let's assume, we answered option A to question 1): template< typename To, typename From > To* memcopy ( To* to, From* from ) precondition{ static_assert(sizeof(To) >= sizeof(From), "destination too small"); static_assert(boost::is_convertible<From, To>::value, "incompatible types"); to; // pointer not null from; // pointer not null } { // ... } Would you expect these static_asserts to be disabled when precondition compilation and checking is turned off at compilation time? I'd think so. Thanks. --Lorenzo

2012/8/30 Lorenzo Caminiti <lorcaminiti@gmail.com>
1) Would you expect to be able to use static_assert within contract assertions (preconditions, postconditions, and class invariants)? Note that only boolean conditions are allowed by N1962 within contracts, general code is not allowed, therefore this is not a trivial question.
For example, would you expect to be able to do the following option A:
template< typename To, typename From > To* memcopy ( To* to, From* from ) precondition{ static_assert(sizeof(To) >= sizeof(From), "destination too small"); static_assert(boost::is_convertible<From, To>::value, "incompatible types"); to; // pointer not null from; // pointer not null } { // ... }
In this case, would it ever make sense to use static_assert in postcondition and/or class invariants even if that were allowed?
This is an interesting example. Here I can see the value of static asserts as contracts. But as you observe, it looks that they only make sense as precondtions: function "expects" and caller "ensures". It is difficult to imagine that a function could "ensure" anything itself at compile-time (that you couldn't check yourself). Invariants, as contract, are somewhat closer to pstconditions. But for those "static preconditions" there exist better tools. For instance "static if" proposed for C++17: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3329.pdf In section 2.4 it describes sort of "small concepts". Using this proposal, your example could be rewritten to: template< typename To, typename From > To* memcopy ( To* to, From* from ) if (sizeof(To) >= sizeof(From) && is_convertible<From, To>::value) precondition{ to != nullptr; from != nullptr }
{ // ... }
This works somewhat different though. It makes the function not even defined for incompatible types (error message would be "no matching function found"). The assertion variant does recognize that you intended to call our function and tells you exactly why it was a bad idea. You can implement this behavior in C++03 with boost::enable_if, I guess.
Or would you just use static_assert in the body, option B:
template< typename To, typename From > To* memcopy ( To* to, From* from ) precondition{ to; // pointer not null from; // pointer not null } { static_assert(sizeof(To) >= sizeof(From), "destination too small"); static_assert(boost::is_convertible<From, To>::value, "incompatible types"); // ... }
I don't like this because the assertions are about the specifications (they assert a requirement of the interface) so they should go in the declaration and not in the implementation.
I agree that this is worse.
Or maybe the assertions should be represented as concept requirements on To and From, option C:
template< typename To, typename From > requires SizeofGreaterEqual<To, From>, Convertible<From, To> To* memcopy ( To* to, From* from ) precondition{ to; // pointer not null from; // pointer not null } { // ... }
(Only we don't have concepts...)
But we have enable_if which is sufficient. It has an ugly syntax, but I guess your framework could make it simpler. But then, even with full concepts in C++ assertions have an advantage: they give you a cleaner error message.
2) Let's assume, we answered option A to question 1):
template< typename To, typename From > To* memcopy ( To* to, From* from ) precondition{ static_assert(sizeof(To) >= sizeof(From), "destination too small"); static_assert(boost::is_convertible<From, To>::value, "incompatible types"); to; // pointer not null from; // pointer not null } { // ... }
Would you expect these static_asserts to be disabled when precondition compilation and checking is turned off at compilation time? I'd think so.
Are you asking (a) what I would want to have, or (b) what I would expect if I have seen this syntax? If (a): As I understand it, people consider disabling assertions, in order to avoid run-time overhead. Since static assertions add no overhead I would never want to disable them. If (b): the way assertions are grouped in the example, it might suggest that disabling precondition would disable both kinds of assertions. Regards, &rzej

On Aug 31, 2012 5:42 AM, "Andrzej Krzemienski" <akrzemi1@gmail.com> wrote:
2012/8/30 Lorenzo Caminiti <lorcaminiti@gmail.com>
1) Would you expect to be able to use static_assert within contract assertions (preconditions, postconditions, and class invariants)? Note that only boolean conditions are allowed by N1962 within contracts, general code is not allowed, therefore this is not a trivial question.
For example, would you expect to be able to do the following option A:
template< typename To, typename From > To* memcopy ( To* to, From* from ) precondition{ static_assert(sizeof(To) >= sizeof(From), "destination too small"); static_assert(boost::is_convertible<From, To>::value, "incompatible types"); to; // pointer not null from; // pointer not null } { // ... }
In this case, would it ever make sense to use static_assert in postcondition and/or class invariants even if that were allowed?
This is an interesting example. Here I can see the value of static asserts as contracts. But as you observe, it looks that they only make sense as precondtions: function "expects" and caller "ensures". It is difficult to imagine that a function could "ensure" anything itself at compile-time (that you couldn't check yourself). Invariants, as contract, are somewhat closer to pstconditions.
But for those "static preconditions" there exist better tools. For
instance
"static if" proposed for C++17: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3329.pdf In section 2.4 it describes sort of "small concepts". Using this proposal, your example could be rewritten to:
template< typename To, typename From > To* memcopy ( To* to, From* from ) if (sizeof(To) >= sizeof(From) && is_convertible<From, To>::value) precondition{ to != nullptr; from != nullptr }
{ // ... }
This works somewhat different though. It makes the function not even defined for incompatible types (error message would be "no matching function found"). The assertion variant does recognize that you intended to call our function and tells you exactly why it was a bad idea.
You can implement this behavior in C++03 with boost::enable_if, I guess.
Or would you just use static_assert in the body, option B:
template< typename To, typename From > To* memcopy ( To* to, From* from ) precondition{ to; // pointer not null from; // pointer not null } { static_assert(sizeof(To) >= sizeof(From), "destination too
small");
static_assert(boost::is_convertible<From, To>::value, "incompatible types"); // ... }
I don't like this because the assertions are about the specifications (they assert a requirement of the interface) so they should go in the declaration and not in the implementation.
I agree that this is worse.
Or maybe the assertions should be represented as concept requirements on To and From, option C:
template< typename To, typename From > requires SizeofGreaterEqual<To, From>, Convertible<From, To> To* memcopy ( To* to, From* from ) precondition{ to; // pointer not null from; // pointer not null } { // ... }
(Only we don't have concepts...)
But we have enable_if which is sufficient. It has an ugly syntax, but I guess your framework could make it simpler. But then, even with full concepts in C++ assertions have an advantage: they give you a cleaner error message.
Based on your and Vicente's input, I'll remove static_assert from contracts (pre, post, inv, etc). Is any predicate programmable with static_assert also programmable with Boost.ConceptCheck? I guess so if the Boost.ConceptCheck concept can do the static assertion using Boost.MPL or Boost.StaticAssert. If that it true then no need for me to provide static_assert at all, just use Boost.ConceptCheck. Otherwise, I'll allow static_assert together with Boost.ConceptCheck concepts in the requires clausle. How does that sound?
2) Let's assume, we answered option A to question 1):
template< typename To, typename From > To* memcopy ( To* to, From* from ) precondition{ static_assert(sizeof(To) >= sizeof(From), "destination too small"); static_assert(boost::is_convertible<From, To>::value, "incompatible types"); to; // pointer not null from; // pointer not null } { // ... }
Would you expect these static_asserts to be disabled when precondition compilation and checking is turned off at compilation time? I'd think so.
Are you asking (a) what I would want to have, or (b) what I would expect if I have seen this syntax? If (a): As I understand it, people consider disabling assertions, in order to avoid run-time overhead. Since static assertions add no overhead I would never want to disable them.
If (b): the way assertions are grouped in the example, it might suggest that disabling precondition would disable both kinds of assertions.
Regards, &rzej
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
--Lorenzo

Based on your and Vicente's input, I'll remove static_assert from contracts (pre, post, inv, etc).
Is any predicate programmable with static_assert also programmable with Boost.ConceptCheck? I guess so if the Boost.ConceptCheck concept can do the static assertion using Boost.MPL or Boost.StaticAssert. If that it true then no need for me to provide static_assert at all, just use Boost.ConceptCheck. Otherwise, I'll allow static_assert together with Boost.ConceptCheck concepts in the requires clausle. How does that sound?
I do not know if Boost.ConceptCheck offers the capability of verifying any boolean predicate. I know that concepts proposal (N2081) offered a special concept True for that purpose (not sure if it is definable in Boost.ConceptCheck). As a side note, in the new attempt at concept design described in N3331, any concept is a boolean predicate by definition; thus you can constraint template like this: template <typename T> requires InputIterator<T> && sizeof(T) > sizeof(void*) ... I believe that whatever the answer to your question is, your idea to enable static asserts along concept checking adds some value (let others judge ho much): with concept checks you can only eliminate your template (or template specialization) from overload resolution set and see what happens: perhaps another overload will be picked and the compilation will succeed. With static assertions, on the other hand, your template is _always_ always preferred only to trigger compilation error the second after: this is often useful when you want to provide clear error messages and you know there will be no other overload. For instance, if I write a binary number literal, and I want to specify at compile-time that my literal can only accept 0s or 1s, I would never use concepts or sfinae tricks: template <char... C> requires Only0sAnd1s<C...>::value constexpr unsigned operator "" _B(); Instead, I would prefer to write: template <char... C> constexpr unsigned operator "" _B() { static_assert( Only0sAnd1s<C...>::value, "only 0s and 1s allowed in binary literal" ); return ... } Regards, &rzej

On Mon, Sep 3, 2012 at 12:48 AM, Andrzej Krzemienski <akrzemi1@gmail.com> wrote:
Based on your and Vicente's input, I'll remove static_assert from contracts (pre, post, inv, etc).
Is any predicate programmable with static_assert also programmable with Boost.ConceptCheck? I guess so if the Boost.ConceptCheck concept can do the static assertion using Boost.MPL or Boost.StaticAssert. If that it true then no need for me to provide static_assert at all, just use Boost.ConceptCheck. Otherwise, I'll allow static_assert together with Boost.ConceptCheck concepts in the requires clausle. How does that sound?
I do not know if Boost.ConceptCheck offers the capability of verifying any boolean predicate. I know that concepts proposal (N2081) offered a special concept True for that purpose (not sure if it is definable in Boost.ConceptCheck). As a side note, in the new attempt at concept design described in N3331,
OK, I need to study N3331.
any concept is a boolean predicate by definition; thus you can constraint template like this:
template <typename T> requires InputIterator<T> && sizeof(T) > sizeof(void*) ...
I believe that whatever the answer to your question is, your idea to enable static asserts along concept checking adds some value (let others judge ho much): with concept checks you can only eliminate your template (or template specialization) from overload resolution set and see what happens: perhaps another overload will be picked and the compilation will succeed. With static assertions, on the other hand, your template is _always_ always preferred only to trigger compilation error the second after: this is often useful when you want to provide clear error messages and you know there will be no other overload. For instance, if I write a binary number literal, and I want to specify at compile-time that my literal can only accept 0s or 1s, I would never use concepts or sfinae tricks:
template <char... C> requires Only0sAnd1s<C...>::value constexpr unsigned operator "" _B();
Instead, I would prefer to write:
template <char... C> constexpr unsigned operator "" _B() { static_assert( Only0sAnd1s<C...>::value, "only 0s and 1s allowed in binary literal" ); return ... }
Thanks, --Lorenzo

I do not know if Boost.ConceptCheck offers the capability of verifying any boolean predicate. I know that concepts proposal (N2081) offered a special concept True for that purpose (not sure if it is definable in Boost.ConceptCheck). As a side note, in the new attempt at concept design described in N3331,
OK, I need to study N3331.
I meant N3351 (<http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3351.pdf>"A Concept Design for the STL"), sorry. Regards, &rzej

on Mon Sep 03 2012, Andrzej Krzemienski <akrzemi1-AT-gmail.com> wrote:
I do not know if Boost.ConceptCheck offers the capability of verifying any boolean predicate.
Sure; even if the concept isn't already in the library, you can easily write it. -- Dave Abrahams BoostPro Computing Software Development Training http://www.boostpro.com Clang/LLVM/EDG Compilers C++ Boost

On Sun, Sep 16, 2012 at 6:31 PM, Dave Abrahams <dave@boostpro.com> wrote:
on Mon Sep 03 2012, Andrzej Krzemienski <akrzemi1-AT-gmail.com> wrote:
I do not know if Boost.ConceptCheck offers the capability of verifying any boolean predicate.
Sure; even if the concept isn't already in the library, you can easily write it.
So if we can program a static boolean predicate then we can program a ConceptCheck concept that will statically assert it and make it fail at compile-time. I guess however that the opposite is not true. There are conditions that can be programmed to generate a hard compiler failure (e.g., using ConceptCheck) but we cannot create a boolean meta-function for them (e.g., to use enable-if and/or SFINAE-like concepts). For example we can program CopyConstructible but we cannot program has_copy_constructor--am I correct? Is that true with C++11 expression SFINAE as well? If that is the case, there can be type requirements that we can only program using hard-failing-concepts (ConceptCheck concepts within Contract's check clause) but not using SFINAE-concepts (possible future Boost.Contract's requires clause). Thanks, --Lorenzo

Le 17/09/12 05:26, Lorenzo Caminiti a écrit :
on Mon Sep 03 2012, Andrzej Krzemienski <akrzemi1-AT-gmail.com> wrote:
I do not know if Boost.ConceptCheck offers the capability of verifying any boolean predicate. Sure; even if the concept isn't already in the library, you can easily write it. So if we can program a static boolean predicate then we can program a ConceptCheck concept that will statically assert it and make it fail at compile-time. I guess however that the opposite is not true. There are conditions that can be programmed to generate a hard compiler failure (e.g., using ConceptCheck) but we cannot create a boolean
On Sun, Sep 16, 2012 at 6:31 PM, Dave Abrahams <dave@boostpro.com> wrote: meta-function for them (e.g., to use enable-if and/or SFINAE-like concepts).
For example we can program CopyConstructible but we cannot program has_copy_constructor--am I correct? Is that true with C++11 expression SFINAE as well? This depends on the c++ version as some traits need compiler help. In C++11 there are some traits as has_copy_constructor, has_trivial_copy_constructtor, ... If that is the case, there can be type requirements that we can only program using hard-failing-concepts (ConceptCheck concepts within Contract's check clause) but not using SFINAE-concepts (possible future Boost.Contract's requires clause).
You are surely right, but it is less evident for c++11. Do you have an example? Best, Vicente

On Sun, Sep 16, 2012 at 11:56 PM, Vicente J. Botet Escriba <vicente.botet@wanadoo.fr> wrote:
Le 17/09/12 05:26, Lorenzo Caminiti a écrit :
On Sun, Sep 16, 2012 at 6:31 PM, Dave Abrahams <dave@boostpro.com> wrote:
on Mon Sep 03 2012, Andrzej Krzemienski <akrzemi1-AT-gmail.com> wrote:
I do not know if Boost.ConceptCheck offers the capability of verifying any boolean predicate.
Sure; even if the concept isn't already in the library, you can easily write it.
So if we can program a static boolean predicate then we can program a ConceptCheck concept that will statically assert it and make it fail at compile-time. I guess however that the opposite is not true. There are conditions that can be programmed to generate a hard compiler failure (e.g., using ConceptCheck) but we cannot create a boolean meta-function for them (e.g., to use enable-if and/or SFINAE-like concepts).
For example we can program CopyConstructible but we cannot program has_copy_constructor--am I correct? Is that true with C++11 expression SFINAE as well?
This depends on the c++ version as some traits need compiler help.
Compiler help outside the standard (either C++03 or C++11) doesn't count ;)
In C++11 there are some traits as has_copy_constructor, has_trivial_copy_constructtor, ...
True.
If that is the case, there can be type requirements that we can only program using hard-failing-concepts (ConceptCheck concepts within Contract's check clause) but not using SFINAE-concepts (possible future Boost.Contract's requires clause).
You are surely right, but it is less evident for c++11.
True.
Do you have an example?
Not on top of my head, I'll probably have examples after studying N3351. In any case, this is just an argument to have /both/ hard-error-concepts and enable-if-concepts (if Boost.Contract will ever support both, I'll have to document why that is the case). BTW, now that I'm thinking about it, if you use Boost.Contract to declare a class/function, I /might/ be able to expand some extra code that will support compile-time trait inspection... For example, has_copy_constructor /could/ be made to work for a constructor declared using CONTRACT_CONSTRUCTOR even on C++03 and without extra compiler help... I'll look into this if/when extending Boost.Contract to support N3351-like concepts. Thanks, --Lorenzo

On Sun, Sep 16, 2012 at 11:56 PM, Vicente J. Botet Escriba <vicente.botet@wanadoo.fr> wrote:
Le 17/09/12 05:26, Lorenzo Caminiti a écrit :
on Mon Sep 03 2012, Andrzej Krzemienski <akrzemi1-AT-gmail.com> wrote:
I do not know if Boost.ConceptCheck offers the capability of verifying any boolean predicate. Sure; even if the concept isn't already in the library, you can easily write it. So if we can program a static boolean predicate then we can program a ConceptCheck concept that will statically assert it and make it fail at compile-time. I guess however that the opposite is not true. There are conditions that can be programmed to generate a hard compiler failure (e.g., using ConceptCheck) but we cannot create a boolean
On Sun, Sep 16, 2012 at 6:31 PM, Dave Abrahams <dave@boostpro.com> wrote: meta-function for them (e.g., to use enable-if and/or SFINAE-like concepts).
For example we can program CopyConstructible but we cannot program has_copy_constructor--am I correct? Is that true with C++11 expression SFINAE as well? This depends on the c++ version as some traits need compiler help. Compiler help outside the standard (either C++03 or C++11) doesn't count ;) I'm referring to traits included in the standard. See 20.9.4.3 Type
Le 18/09/12 06:14, Lorenzo Caminiti a écrit : properties [meta.unary.prop] C++ International Standard
In C++11 there are some traits as has_copy_constructor, has_trivial_copy_constructtor, ... True.
Sorry, the names are is_copy_constructible, is trivially_copyable, ...
If that is the case, there can be type requirements that we can only program using hard-failing-concepts (ConceptCheck concepts within Contract's check clause) but not using SFINAE-concepts (possible future Boost.Contract's requires clause).
You are surely right, but it is less evident for c++11. True.
Do you have an example? Not on top of my head, I'll probably have examples after studying N3351. In any case, this is just an argument to have /both/ hard-error-concepts and enable-if-concepts
I don't know if this this is a valid argument. I would say that we need "enable_if" to avoid instantiation that will result on compiler error and static assertions to report bad instantiations at compile time.
(if Boost.Contract will ever support both, I'll have to document why that is the case). I guess the preceding is just a good justification.
BTW, now that I'm thinking about it, if you use Boost.Contract to declare a class/function, I /might/ be able to expand some extra code that will support compile-time trait inspection... For example, has_copy_constructor /could/ be made to work for a constructor declared using CONTRACT_CONSTRUCTOR even on C++03 and without extra compiler help... I'll look into this if/when extending Boost.Contract to support N3351-like concepts.
Yeah, this will be great. I think this could be done before including enable_if as the user can take advantage of these traits independently of whether the class's user uses Boost.Contract or not. I you want I could help proposing the addition of these traits in Boost.TypeTraits so that they can be specialized by the user and by Boost.Contract in particular. Best, Vicente

on Thu Aug 30 2012, Lorenzo Caminiti <lorcaminiti-AT-gmail.com> wrote:
In this case, would it ever make sense to use static_assert in postcondition and/or class invariants even if that were allowed?
The only good excuse I can think of for doing so is that you happen to have calculated some type or constant needed for the assertion in the scope of the postcondition and/or class invariant, and you want to keep it there. Not terribly compelling unless you can show there's a large class of examples like this, IMO. -- Dave Abrahams BoostPro Computing Software Development Training http://www.boostpro.com Clang/LLVM/EDG Compilers C++ Boost
participants (4)
-
Andrzej Krzemienski
-
Dave Abrahams
-
Lorenzo Caminiti
-
Vicente J. Botet Escriba