Boost logo

Boost :

Subject: [boost] [contract] concept error messages
From: Lorenzo Caminiti (lorcaminiti_at_[hidden])
Date: 2012-10-05 20:06:48


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


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