
Hello all, Main purposes of concepts are to (a) document the type requirements and (b) generate useful error messages when a type does not met the requirements. I'd like to discuss how the ideal error messages should look like when a given type does not model the specified concepts. One point of discussion is: In case a type doesn't model a concept, should the compiler generate an hard error or the function/class template should simply be removed from the overload/specialization set? For example, consider: template< typename T> requires EqualityComparable<T> bool equal ( T a, T b ) // #1 { return a == b; // #3 } ... struct x { explicit x(char const*); }; x abc("abc"); equal(abc, abc); // #2 Let's assume we want an hard error then I think ideally the compiler should generate two errors: 1) An error at line #1 indicating that the concept EqualityComparable<T> is not modeled. Ideally this error will read: line #1: failed to model concept EqualityComparable<T> 2) An error at line #2 indicating that the call equal(abc, abc) cannot be resolved. Ideally this error will read: line #2: no matching call for function equal 3) Note that no error should be generated from within the equal function implementation (line #3, etc) because the concept is not modeled so the function definition should not be compiled. Now, this is what I was able to actually implement. === Option A) Using enable_if === template< typename T > typename std::enable_if< AlwaysTrue<T>::value && contract::std::EqualityComparable<T>::value, bool>::type equal1 ( T const& a, T const& b ) { return a == b; } ... equal1(abc, abc); // 08.cpp:44 08.cpp:44:5: error: no matching function for call to 'equal1' equal1(abc, abc); ^~~~~~ [...] This error is not ideal because it only satisfies 2) and 3) but not 1). It does not give information about which concept was not modeled -- you don't see EqualityComparable or "concept failed" anywhere in the error. (Clang actually mentions EqualityComparable in a "note" following the error (see below) but that's not a message that can be relied on across different compilers.) Users can use this approach if they don't want an hard error but they only want to remove the function from the overload set on concept failure. === Option B) Using concept_check === template< typename T > typename contract::concept_check< AlwaysTrue<T>::value, contract::std::EqualityComparable<T>::value, bool>::type equal2 ( T const& a, T const& b ) { return a == b; } ... equal2(abc, abc); // 08.cpp:45 In file included from 08.cpp:5: include/contract/concept_check.hpp:61:5: error: static_assert failed "model concept number 2" static_assert(Concept2, "model concept number 2"); ^ ~~~~~~~~ [...] 08.cpp:45:5: error: no matching function for call to 'equal2' equal2(abc, abc); ^~~~~~ This also satisfies 2) and 3) however it only partially satisfies 1). The error mentions the number of the concept "2" but not its name EqualityComparable (that's probably OK) but the error line number refers to the file implementing concept_check and not to the point of declaration of equal2. In other words, the error for 1) does not indicate line #1 (that's probably acceptable but not ideal). Note that the error for 1) is generate using a static_assert so it is portable. === Option C) Using CONCEPT_CHECK macro === CONTRACT_CONCEPT_CHECK( // 08.cpp:26 (template< typename T >), (AlwaysTrue<T>) (contract::std::EqualityComparable<T>), bool, equal3 ) ( T const& a, T const& b ) { return a == b; } ... equal3(abc, abc); // 08.cpp:45 08.cpp:26:1: error: static_assert failed "model concept number 2: contract::std::EqualityComparable<T>" CONTRACT_CONCEPT_CHECK( ^~~~~~~~~~~~~~~~~~~~~~~ [...] 08.cpp:46:5: error: no matching function for call to 'equal3' equal3(abc, abc); ^~~~~~ [...] These errors satisfy all 1), 2), and 3) however the use of macros makes clang generate lots of "notes" to backtrace the macro expansion (there's probably a way to disable these notes, plus they are not actual errors and can be ignored). Note that the concept name EqualityComparable appears in the static assertion error message so it is guaranteed to be printed portably among compilers. I'm thinking that these are all valid ways to check concepts. Eventually, Boost.Contract macros will use B) (given that the lib already uses macros to declare functions). However, if users want to use Boost.Contract macros to declare a concept but they want to use enable_if or contract::concept_check to check it, they can do so (in case they don't want to use the lib macros to declare their functions). Complete error messages (including "notes"): 08.cpp:44:5: error: no matching function for call to 'equal1' equal1(abc, abc); ^~~~~~ 08.cpp:12:5: note: candidate template ignored: disabled by 'enable_if' [with T = x] AlwaysTrue<T>::value && contract::std::EqualityComparable<T>::value, ^ In file included from 08.cpp:5: include/contract/concept_check.hpp:61:5: error: static_assert failed "model concept number 2" static_assert(Concept2, "model concept number 2"); ^ ~~~~~~~~ 08.cpp:19:20: note: in instantiation of template class 'contract::concept_check<true, false, bool>' requested here typename contract::concept_check< ^ 08.cpp:21:13: note: while substituting deduced template arguments into function template 'equal2' [with T = x] bool>::type equal2 ( T const& a, T const& b ) ^ 08.cpp:45:5: error: no matching function for call to 'equal2' equal2(abc, abc); ^~~~~~ 08.cpp:21:13: note: candidate template ignored: substitution failure [with T = x] bool>::type equal2 ( T const& a, T const& b ) ^ 08.cpp:26:1: error: static_assert failed "model concept number 2: contract::std::EqualityComparable<T>" CONTRACT_CONCEPT_CHECK( ^~~~~~~~~~~~~~~~~~~~~~~ include/contract/concept_check.hpp:40:33: note: expanded from macro 'CONTRACT_CONCEPT_CHECK' BOOST_PP_SEQ_FOR_EACH_I(CONTRACT_CONCEPT_CHECK_ASSERT_, ~, concepts) \ ^ ../../../boost_1_50_0.cyg/boost/preprocessor/seq/for_each_i.hpp:27:69: note: expanded from macro 'BOOST_PP_SEQ_FOR_EACH_I' # define BOOST_PP_SEQ_FOR_EACH_I(macro, data, seq) BOOST_PP_FOR((macro, data, seq (nil), 0), BOOST_PP_SEQ_FOR_EACH_I_P, BOOST_PP_SEQ_FOR_EACH_I_O, BOOST_PP_SEQ_FOR_EACH_I_M) ^ ../../../boost_1_50_0.cyg/boost/preprocessor/repetition/detail/for.hpp:22:78: note: expanded from macro 'BOOST_PP_FOR_1' # define BOOST_PP_FOR_1(s, p, o, m) BOOST_PP_FOR_1_C(BOOST_PP_BOOL(p(2, s)), s, p, o, m) ^ note: (skipping 7 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all) ../../../boost_1_50_0.cyg/boost/preprocessor/seq/for_each_i.hpp:45:80: note: expanded from macro 'BOOST_PP_SEQ_FOR_EACH_I_M_IM' # define BOOST_PP_SEQ_FOR_EACH_I_M_IM(r, im) BOOST_PP_SEQ_FOR_EACH_I_M_I(r, im) ^ ../../../boost_1_50_0.cyg/boost/preprocessor/seq/for_each_i.hpp:50:62: note: expanded from macro 'BOOST_PP_SEQ_FOR_EACH_I_M_I' # define BOOST_PP_SEQ_FOR_EACH_I_M_I(r, macro, data, seq, i) macro(r, data, i, BOOST_PP_SEQ_HEAD(seq)) ^ include/contract/concept_check.hpp:23:5: note: expanded from macro 'CONTRACT_CONCEPT_CHECK_ASSERT_' static_assert(CONTRACT_CONCEPT_CHECK_COND_(i), "model concept number " \ ^ 08.cpp:26:1: note: in instantiation of template class 'contractXconceptXcheckXequal3X30<true, false>' requested here CONTRACT_CONCEPT_CHECK( ^ include/contract/concept_check.hpp:50:14: note: expanded from macro 'CONTRACT_CONCEPT_CHECK' typename CONTRACT_CONCEPT_CHECK_CLASS_(func_name)< \ ^ include/contract/concept_check.hpp:11:23: note: expanded from macro 'CONTRACT_CONCEPT_CHECK_CLASS_' BOOST_PP_SEQ_CAT((contractXconceptXcheckX)(func_name)(X)(__LINE__)) ^ ../../../boost_1_50_0.cyg/boost/preprocessor/seq/cat.hpp:30:7: note: expanded from macro 'BOOST_PP_SEQ_CAT' )(seq) \ ^ note: (skipping 18 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all) ../../../boost_1_50_0.cyg/boost/preprocessor/seq/cat.hpp:35:37: note: expanded from macro 'BOOST_PP_SEQ_CAT_O_I' # define BOOST_PP_SEQ_CAT_O_I(a, b) a ## b ^ <scratch space>:91:1: note: expanded from macro 'contractXconceptXcheckXequal3X' contractXconceptXcheckXequal3X30 ^ ../../../boost_1_50_0.cyg/boost/preprocessor/seq/fold_left.hpp:295:51: note: expanded from macro 'BOOST_PP_SEQ_FOLD_LEFT_F' # define BOOST_PP_SEQ_FOLD_LEFT_F(op, st, ss, sz) st ^ 08.cpp:29:11: note: while substituting deduced template arguments into function template 'equal3' [with T = x] bool, equal3 ^ include/contract/concept_check.hpp:53:5: note: expanded from macro 'CONTRACT_CONCEPT_CHECK' func_name ^ 08.cpp:46:5: error: no matching function for call to 'equal3' equal3(abc, abc); ^~~~~~ 08.cpp:29:11: note: candidate template ignored: substitution failure [with T = x] bool, equal3 ^ include/contract/concept_check.hpp:53:5: note: expanded from macro 'CONTRACT_CONCEPT_CHECK' func_name ^ 5 errors generated. --Lorenzo

One point of discussion is: In case a type doesn't model a concept, should the compiler generate an hard error or the function/class template should simply be removed from the overload/specialization set?
I think it's best to remove templates from the overload set or specialization matching. In other words, check constraints early and check constraints often. You should only really get "hard" concept errors when you actually end up trying to use a bad overload or specialization. Andrew

On Fri, Oct 5, 2012 at 5:23 PM, Andrew Sutton <asutton.list@gmail.com> wrote:
One point of discussion is: In case a type doesn't model a concept, should the compiler generate an hard error or the function/class template should simply be removed from the overload/specialization set?
I think it's best to remove templates from the overload set or specialization matching. In other words, check constraints early and check constraints often. You should only really get "hard" concept errors when you actually end up trying to use a bad overload or specialization.
Using concept (boolean meta-functions) with enable_if should be sufficient then -- just option A): template< typename T > typename std::enable_if< AlwaysTrue<T>::value && contract::std::EqualityComparable<T>::value, bool>::type equal1 ( T const& a, T const& b ) { return a == b; } And given this, I personally don't see the need for specifying concepts in place of typename or using requires. In other words, I think the above use of enable_if is sufficient and the following syntax is not necessary (even if it's part of N3351): template< AlwaysTrue T > requires contract::std::EqualityComparable<T> bool equal1 ( T const& a, T const& b ) { return a == b; } What would be the purpose of supporting this syntax given that we can equivalently use enable_if (which is already part of the standard)? (I'm only looking at function templates for now, I'm not sure how the "enable_if" equivalent for class templates will look like and that might be a reason for using the typename/requires syntax to specify concepts.) On some compilers like clang, enable_if might even generate errors that show the concept signature in a note (even if that's not guaranteed on all compilers because it is not in general part of the call failure message): 08.cpp:44:5: error: no matching function for call to 'equal1' equal1(abc, abc); ^~~~~~ 08.cpp:12:5: note: candidate template ignored: disabled by 'enable_if' [with T = x] AlwaysTrue<T>::value && contract::std::EqualityComparable<T>::value, ^ Thanks, --Lorenzo

template< typename T > typename std::enable_if< AlwaysTrue<T>::value && contract::std::EqualityComparable<T>::value, bool>::type equal1 ( T const& a, T const& b ) { return a == b; }
Just curious, but what is the purpose of AlwaysTrue? If it's always true, why not omit it?
And given this, I personally don't see the need for specifying concepts in place of typename or using requires. In other words, I think the above use of enable_if is sufficient and the following syntax is not necessary (even if it's part of N3351):
template< AlwaysTrue T > requires contract::std::EqualityComparable<T> bool equal1 ( T const& a, T const& b ) { return a == b; }
From a technical perspective, how would you specify this behavior for
Lots of reasons, but mostly it clearly delineates what is required from what is returned. the standard? Can you do it in a way that guarantees the same behavior for all of the following, equivalent declarations? // std::enable_if template <typename T> typename std::enable_if<Eq<T>::value, bool>::type f(T x) // Boost enable_if template <typename T> typename boost::enable_if<Eq<T>, bool>::type f(T x) // Origin style template <typename T> Requires<Eq<T>(), bool> f(T x) Should the compiler know that all of these are the same? How could you guarantee that? Would the behavior be different for the std::enable_if? If Requires was an alias to std::enable_if (it is), does that get the same behavior? Libraries are great for helping figure out what you want, but you're going to hit a wall at some point. I think "requires" is one of those walls. Andrew

On Sat, Oct 6, 2012 at 11:29 AM, Andrew Sutton <asutton.list@gmail.com> wrote:
template< typename T > typename std::enable_if< AlwaysTrue<T>::value && contract::std::EqualityComparable<T>::value, bool>::type equal1 ( T const& a, T const& b ) { return a == b; }
Just curious, but what is the purpose of AlwaysTrue? If it's always true, why not omit it?
AlwaysTrue is there just so my example has 2 concepts instead of 1. It's just for my own testing, you can ignore it.
And given this, I personally don't see the need for specifying concepts in place of typename or using requires. In other words, I think the above use of enable_if is sufficient and the following syntax is not necessary (even if it's part of N3351):
template< AlwaysTrue T > requires contract::std::EqualityComparable<T> bool equal1 ( T const& a, T const& b ) { return a == b; }
Lots of reasons, but mostly it clearly delineates what is required from what is returned.
But that's a limitation of enable_if for which we should provide language support: template< typename T > bool equal ( ... ) if EqualityComparable<T>::value ; And not a reason to support requires. Plus, I'd assume (all) programmers will become rather familiar with enable_if syntax given that it's in the std.
From a technical perspective, how would you specify this behavior for the standard? Can you do it in a way that guarantees the same behavior for all of the following, equivalent declarations?
// std::enable_if template <typename T> typename std::enable_if<Eq<T>::value, bool>::type f(T x)
// Boost enable_if template <typename T> typename boost::enable_if<Eq<T>, bool>::type f(T x)
// Origin style template <typename T> Requires<Eq<T>(), bool> f(T x)
Should the compiler know that all of these are the same?
Why should the compiler know that? I'm not following...
How could you guarantee that? Would the behavior be different for the std::enable_if? If Requires was an alias to std::enable_if (it is), does that get the same behavior?
Libraries are great for helping figure out what you want, but you're going to hit a wall at some point. I think "requires" is one of those walls.
I still don't understand... given that I can use enable_if (and assuming I find its syntax clear enough), what's a use case that needs requires instead? Thanks, --Lorenzo

On Sat, Oct 6, 2012 at 2:29 PM, Andrew Sutton <asutton.list@gmail.com>wrote:
Lots of reasons, but mostly it clearly delineates what is required from what is returned.
I agree with you that requires is important, particularly for concept-based overloads where one overload has more refined concepts than another, though in C++11, you no longer need to use enable_if in the return type or function parameter list, so obfuscation of the function type is not as much of a concern as it used to be. For instance, if you use the enable_if library in the sandbox you can simply do: template< class T, BOOST_ENABLE_IF( contract::std::EqualityComparable< T > ) > bool equal1( T const& a, T const& b ); -- -Matt Calabrese

On Sat, Oct 6, 2012 at 5:06 PM, Matt Calabrese <rivorus@gmail.com> wrote:
On Sat, Oct 6, 2012 at 2:29 PM, Andrew Sutton <asutton.list@gmail.com>wrote:
Lots of reasons, but mostly it clearly delineates what is required from what is returned.
I agree with you that requires is important, particularly for concept-based overloads where one overload has more refined concepts than another
Can you guys give me an example of this? Thanks. --Lorenzo

On Sun, Oct 7, 2012 at 1:08 PM, Lorenzo Caminiti <lorcaminiti@gmail.com>wrote:
On Sat, Oct 6, 2012 at 5:06 PM, Matt Calabrese <rivorus@gmail.com> wrote:
On Sat, Oct 6, 2012 at 2:29 PM, Andrew Sutton <asutton.list@gmail.com wrote:
Lots of reasons, but mostly it clearly delineates what is required from what is returned.
I agree with you that requires is important, particularly for concept-based overloads where one overload has more refined concepts than another
Can you guys give me an example of this?
std::advance, even in the C++98 standard library, is overloaded differently for input iterators than it is from bidirectional and random access iterators. With random access iterators, advance operates in constant time. With input iterators, advance operates linearly. The way this is currently implemented is via tag-dispatching -- on a call to std::advance, the iterator category is taken from iterator traits of the iterator argument and is used as an argument to a function with multiple overloads based on the iterator category that is passed (iterator_categories are related by inheritance, so even if you make an entirely new iterator category that is derived from the random access iterator category, the random access overload will still be picked). You can't simply emulate this behavior with enable_if by having a single enabler for random access iterators and a single enabler_for input iterators, etc. because a random access iterator IS an input iterator. The overload would be ambiguous. With a language-level concepts feature, you can get concept-based overloading to work entirely in the expected manner with no need for iterator categories, no need for tag dispatching, and the implementation works fine in the presence of user-provided concepts that refine standard iterator concepts. -- -Matt Calabrese

On Sun, Oct 7, 2012 at 10:33 AM, Matt Calabrese <rivorus@gmail.com> wrote:
On Sun, Oct 7, 2012 at 1:08 PM, Lorenzo Caminiti <lorcaminiti@gmail.com>wrote:
On Sat, Oct 6, 2012 at 5:06 PM, Matt Calabrese <rivorus@gmail.com> wrote:
On Sat, Oct 6, 2012 at 2:29 PM, Andrew Sutton <asutton.list@gmail.com wrote:
Lots of reasons, but mostly it clearly delineates what is required from what is returned.
I agree with you that requires is important, particularly for concept-based overloads where one overload has more refined concepts than another
Can you guys give me an example of this?
std::advance, even in the C++98 standard library, is overloaded differently for input iterators than it is from bidirectional and random access iterators. With random access iterators, advance operates in constant time. With input iterators, advance operates linearly.
Sure, I understand: template< class I, class Distance > requries InputIterator<I> void advance( I& it, Distance n ) { /* linear complexity... */ } template< class I, class Distance > requries BidirectionalIterator<I> void advance( I& it, Distance n ) { /* ... */ } template< class I, class Distance > requries RandomAccessIterator<I> void advance( I& it, Distance n ) { /* constant complexity... */ } I agree, this type of overloading is a reason for having requires, enable_if will not suffice. Is there any other reason?
The way this is currently implemented is via tag-dispatching -- on a call to std::advance, the iterator category is taken from iterator traits of the iterator argument and is used as an argument to a function with multiple overloads based on the iterator category that is passed (iterator_categories are related by inheritance, so even if you make an entirely new iterator category that is derived from the random access iterator category, the random access overload will still be picked). You can't simply emulate this behavior with enable_if by having a single enabler for random access iterators and a single enabler_for input iterators, etc. because a random access iterator IS an input iterator. The overload would be ambiguous. With a language-level concepts feature, you can get concept-based overloading to work entirely in the expected manner with no need for iterator categories, no need for tag dispatching, and the implementation works fine in the presence of user-provided concepts that refine standard iterator concepts.
Thanks, --Lorenzo

On Sun, Oct 7, 2012 at 3:51 PM, Lorenzo Caminiti <lorcaminiti@gmail.com>wrote:
I agree, this type of overloading is a reason for having requires, enable_if will not suffice. Is there any other reason?
With requires (at least the C++0x version of it), you will get a compile-time error if, from within the body of the template you are writing, you attempt to perform an operation that was not specified as a requirement in the requires clause. This error will be given to the user at the time they write the algorithm (even if it is never called) and so the writer of the algorithm can't accidentally assume functionality that may or not be supported by the template's arguments. With enable_if, you are just doing parameter checking, meaning that the body of your function cannot be checked in this manner for validity at the time you write your algorithm. Instead, if the writer of the algorithm makes a mistake, the user of the algorithm will get a messy compile-error inside of the algorithm's body (or the body of something that it calls) at the moment it is instantiated. -- -Matt Calabrese

Lots of reasons, but mostly it clearly delineates what is required from what is returned.
I agree with you that requires is important, particularly for concept-based overloads where one overload has more refined concepts than another
Can you guys give me an example of this?
std::advance, even in the C++98 standard library, is overloaded differently for input iterators than it is from bidirectional and random access iterators. With random access iterators, advance operates in constant time. With input iterators, advance operates linearly.
Sure, I understand:
template< class I, class Distance > requries InputIterator<I> void advance( I& it, Distance n ) { /* linear complexity... */ }
template< class I, class Distance > requries BidirectionalIterator<I> void advance( I& it, Distance n ) { /* ... */ }
template< class I, class Distance > requries RandomAccessIterator<I> void advance( I& it, Distance n ) { /* constant complexity... */ }
I agree, this type of overloading is a reason for having requires, enable_if will not suffice. Is there any other reason?
One (perhaps minor) reason is that enable_if is an ugly workaround rather than a decent solution for the problem. enable_if is a class template. WHen using it in declaring your function template you have to choose how you want to "uglify" your declaration: (1) Uglify the return type? (2) Add a useless function argument? (3) Add a useless template parameter? "requires" on the other hand gives you another section in the declaration that does not interfere with function arguments or return value. This is only a problem of enable_if. If we have "mini-concepts" in form of static-if, the argument above would not hold anymore. Regards, &rzej

On Mon, Oct 8, 2012 at 5:57 AM, Andrzej Krzemienski <akrzemi1@gmail.com> wrote:
Lots of reasons, but mostly it clearly delineates what is required from what is returned.
I agree with you that requires is important, particularly for concept-based overloads where one overload has more refined concepts than another
Can you guys give me an example of this?
std::advance, even in the C++98 standard library, is overloaded differently for input iterators than it is from bidirectional and random access iterators. With random access iterators, advance operates in constant time. With input iterators, advance operates linearly.
Sure, I understand:
template< class I, class Distance > requries InputIterator<I> void advance( I& it, Distance n ) { /* linear complexity... */ }
template< class I, class Distance > requries BidirectionalIterator<I> void advance( I& it, Distance n ) { /* ... */ }
template< class I, class Distance > requries RandomAccessIterator<I> void advance( I& it, Distance n ) { /* constant complexity... */ }
I agree, this type of overloading is a reason for having requires, enable_if will not suffice. Is there any other reason?
One (perhaps minor) reason is that enable_if is an ugly workaround rather than a decent solution for the problem. enable_if is a class template. WHen using it in declaring your function template you have to choose how you want to "uglify" your declaration: (1) Uglify the return type? (2) Add a useless function argument? (3) Add a useless template parameter?
"requires" on the other hand gives you another section in the declaration that does not interfere with function arguments or return value. This is only a problem of enable_if. If we have "mini-concepts" in form of static-if, the argument above would not hold anymore.
This is a problem of enable_if so I don't consider it a rationale for requires. As I briefly mentioned before, if the ugly enable_if is a concern in either forms: template< typename T, BOOST_ENABLE_IF(EqualityComparable<T>)> bool equal ( T const& a, T const& b ); template< typename T > typename std::enable_if<EqualityComparable<T>, bool>::type equal ( T const& a, T const& b ); Then I'll just provide a syntax similar to what is proposed by N3329: BOOST_CONTRACT_FUNCTION( template( typename T ) bool (equal) ( (T const&) a, (T const&) b ) if(EqualityComparable<T>) ) ; And same thing for class templates. Of course, if I end-up providing requires for the reason(s) 1) (and maybe 2)) below then you can use requires instead of enable_if and there's no point for me to also provide this N3329 "if" syntax. 1) Supporting concept based overloading/specializations is a valid argument for having requires as it cannot be done with enable_if (as Matt pointed out): template< class I, class Distance > requries InputIterator<I> void advance( I& it, Distance n ) { /* linear complexity... */ } template< class I, class Distance > requries BidirectionalIterator<I> void advance( I& it, Distance n ) { /* ... */ } template< class I, class Distance > requries RandomAccessIterator<I> void advance( I& it, Distance n ) { /* constant complexity... */ } 2) Another valid argument for requires is that the compiler will give the algorithm implementer an error if he/she uses the type in a way that doesn't model the concept (as Matt pointed out): template< typename T > requires EqualityComparable<T> bool equal ( T const& a, Tconst& b) { T x = a; // compiler error: T is not CopyConstructible, it's just EqualityComparable ... } Unfortunately, I don't think I can do this in a lib :( Can I? Does Boost.Generic provide such errors? Finally: is there any argument other than 1) and 2) for having requires? Thanks! --Lorenzo

On 08/10/12 12:16, Lorenzo Caminiti wrote: <snip>
2) Another valid argument for requires is that the compiler will give the algorithm implementer an error if he/she uses the type in a way that doesn't model the concept (as Matt pointed out):
template< typename T > requires EqualityComparable<T> bool equal ( T const& a, Tconst& b) { T x = a; // compiler error: T is not CopyConstructible, it's just EqualityComparable ... }
Unfortunately, I don't think I can do this in a lib :( Can I? Does Boost.Generic provide such errors?
One way might be to form an archetype and instantiate the template with it. I think Boost.Generic does (or at least aimed to) support the creation of archetypes, but I doubt that it does this. I suspect such an approach would be fraught with complications, but it might be workable. John

On Mon, Oct 8, 2012 at 12:16 PM, Lorenzo Caminiti <lorcaminiti@gmail.com>wrote:
Unfortunately, I don't think I can do this in a lib :( Can I? Does Boost.Generic provide such errors?
It's theoretically possible, to an extent, when I get automatically generate archetypes from the pseudo-signatures working and if people were to use a Boost.Generic macro when writing their constrained algorithms. I was working toward generating archetypes last year, but it's now on hold until I finish fixing explicit concept maps, which is taking a little bit longer than expected. I wasn't expecting to use archetypes to the full extent that they were used in N2914 though because it's complicated to do in a library. What will probably end up happening first in that area is archetypes will be automatically generated from concept definitions, but people will have to manually work with them. With algorithms that have simple constraints specified as template< ((Regular)) T > void foo( T ); it's definitely feasible, but with complicated clauses, it would be difficult. -- -Matt Calabrese

On Mon, Oct 8, 2012 at 1:56 PM, Matt Calabrese <rivorus@gmail.com> wrote:
It's theoretically possible, to an extent, when I get automatically generate archetypes from the pseudo-signatures working and if people were to use a Boost.Generic macro when writing their constrained algorithms. I was working toward generating archetypes last year, but it's now on hold until I finish fixing explicit concept maps, which is taking a little bit longer than expected. I wasn't expecting to use archetypes to the full extent that they were used in N2914 though because it's complicated to do in a library. What will probably end up happening first in that area is archetypes will be automatically generated from concept definitions, but people will have to manually work with them. With algorithms that have simple constraints specified as template< ((Regular)) T > void foo( T ); it's definitely feasible, but with complicated clauses, it would be difficult.
Something that's interesting -- if both of our libraries end up supporting automatic generation of archetypes, we could coordinate how they are looked up and the way we represent them internally so that a single macro for writing algorithm would work with both libraries. I feel like we're both a ways away from that at the moment though. -- -Matt Calabrese

On Mon, Oct 8, 2012 at 11:00 AM, Matt Calabrese <rivorus@gmail.com> wrote:
On Mon, Oct 8, 2012 at 1:56 PM, Matt Calabrese <rivorus@gmail.com> wrote:
It's theoretically possible, to an extent, when I get automatically generate archetypes from the pseudo-signatures working and if people were to use a Boost.Generic macro when writing their constrained algorithms. I was working toward generating archetypes last year, but it's now on hold until I finish fixing explicit concept maps, which is taking a little bit longer than expected. I wasn't expecting to use archetypes to the full extent that they were used in N2914 though because it's complicated to do in a library. What will probably end up happening first in that area is archetypes will be automatically generated from concept definitions, but people will have to manually work with them. With algorithms that have simple constraints specified as template< ((Regular)) T > void foo( T ); it's definitely feasible, but with complicated clauses, it would be difficult.
Something that's interesting -- if both of our libraries end up supporting automatic generation of archetypes, we could coordinate how they are looked up and the way we represent them internally so that a single macro for writing algorithm would work with both libraries. I feel like we're both a ways away from that at the moment though.
Agree it'd be good to coordinate and agree this stuff is probably far away into the future ;) Thanks, --Lorenzo

On Sun, Oct 7, 2012 at 10:33 AM, Matt Calabrese <rivorus@gmail.com> wrote:
With a language-level concepts feature, you can get concept-based overloading to work entirely in the expected manner with no need for iterator categories, no need for tag dispatching, and the implementation works fine in the presence of user-provided concepts that refine standard iterator concepts.
BTW, is this true also for N3351 concepts or just for C++0x concepts? Because iterator tags are still in N3351 and they are even made part of the specification (so they're not just an implementation detail/workaround): concept InputIterator<WeakInputIterator I> = EqualityComparable<I> && Derived<IteratorCategory<I>, input_iterator_tag>; Thanks, --Lorenzo

On Sun, Oct 7, 2012 at 3:58 PM, Lorenzo Caminiti <lorcaminiti@gmail.com>wrote:
BTW, is this true also for N3351 concepts or just for C++0x concepts? Because iterator tags are still in N3351 and they are even made part of the specification (so they're not just an implementation detail/workaround):
N3351 does this because they do not have direct support for explicit concept maps. The problem is, you still need to at least emulate explicit concept maps in their approach, otherwise you can get ambiguity between concepts that have similar requirements but with different semantics/axioms (such as forward iterator and input iterator). The way N3351 disambiguates is they require C++98 style iterator categories -- if a creator of an iterator type associates with it an input_iterator_tag, then it is an input iterator. C++0x's approach removed the need for iterator categories because it directly supports both auto and explicit concepts. -- -Matt Calabrese

On Sun, Oct 7, 2012 at 4:12 PM, Matt Calabrese <rivorus@gmail.com> wrote:
On Sun, Oct 7, 2012 at 3:58 PM, Lorenzo Caminiti <lorcaminiti@gmail.com>wrote:
BTW, is this true also for N3351 concepts or just for C++0x concepts? Because iterator tags are still in N3351 and they are even made part of the specification (so they're not just an implementation detail/workaround):
N3351 does this because they do not have direct support for explicit concept maps. The problem is, you still need to at least emulate explicit concept maps in their approach, otherwise you can get ambiguity between concepts that have similar requirements but with different semantics/axioms (such as forward iterator and input iterator). The way N3351 disambiguates is they require C++98 style iterator categories -- if a creator of an iterator type associates with it an input_iterator_tag, then it is an input iterator. C++0x's approach removed the need for iterator categories because it directly supports both auto and explicit concepts.
To be clear, I believe N3351 purports to support concept-based overloading in the same way C++0x's concepts would have, but because N3351 only has implicit refinement, how matching is done is a little bit less apparent. Actually, the more I think about it, I can't imagine that concept-based overloading would work properly in N3351 when you use their emulated explicit concept maps. The problem is, if you have two concepts with the same requirements other than something like Derived on an associated type, and you had two overloads (one for each of the aforementioned concepts), calls to the overloads would be ambiguous even with requires in the case where you are dealing with an iterator_category style hierarchy for the associated type. This should be true unless N3351-style concepts either decide to support explicit concept maps instead of the Derived/iterator category hack, or alternatively, if the Derived concept is altered to have special meaning when doing matching. -- -Matt Calabrese

On Sun, Oct 7, 2012 at 4:28 PM, Matt Calabrese <rivorus@gmail.com> wrote:
The problem is, if you have two concepts with the same requirements other than something like Derived on an associated type, and you had two overloads (one for each of the aforementioned concepts), calls to the overloads would be ambiguous even with requires in the case where you are dealing with an iterator_category style hierarchy for the associated type. This should be true unless N3351-style concepts either decide to support explicit concept maps instead of the Derived/iterator category hack, or alternatively, if the Derived concept is altered to have special meaning when doing matching.
Ah, nm, I see how this works now in N3351 -- the explicit requirement for the less-refined concept. -- -Matt Calabrese

On 06/10/12 12:03, Lorenzo Caminiti wrote:
On Fri, Oct 5, 2012 at 5:23 PM, Andrew Sutton <asutton.list@gmail.com> wrote:
One point of discussion is: In case a type doesn't model a concept, should the compiler generate an hard error or the function/class template should simply be removed from the overload/specialization set?
I think it's best to remove templates from the overload set or specialization matching. In other words, check constraints early and check constraints often. You should only really get "hard" concept errors when you actually end up trying to use a bad overload or specialization.
Using concept (boolean meta-functions) with enable_if should be sufficient then -- just option A):
template< typename T > typename std::enable_if< AlwaysTrue<T>::value && contract::std::EqualityComparable<T>::value, bool>::type equal1 ( T const& a, T const& b ) { return a == b; }
And given this, I personally don't see the need for specifying concepts in place of typename or using requires. In other words, I think the above use of enable_if is sufficient and the following syntax is not necessary (even if it's part of N3351):
template< AlwaysTrue T > requires contract::std::EqualityComparable<T> bool equal1 ( T const& a, T const& b ) { return a == b; }
What would be the purpose of supporting this syntax given that we can equivalently use enable_if (which is already part of the standard)?
One potential difference is concept refinement. Suppose you have two overloaded functions taking, say, ForwardIterator and RandomAccessIterator. If you pass a type that works for both then for concepts the compiler should pick the RandomAccessIterator overload, but with enable_if it should be ambiguous. John Bytheway
participants (5)
-
Andrew Sutton
-
Andrzej Krzemienski
-
John Bytheway
-
Lorenzo Caminiti
-
Matt Calabrese