In a previous post, I show how to implement advance function using conditional overloading, like this:

FIT_STATIC_LAMBDA_FUNCTION(advance) = fit::conditional( [](auto& it, int n, TICK_PARAM_REQUIRES(tick::trait<is_advanceable>(it, n))) { it += n; }, [](auto& it, int n, TICK_PARAM_REQUIRES(tick::trait<is_decrementable>(it))) { if (n > 0) while (n--) ++it; else { n *= -1; while (n--) --it; } }, [](auto& it, int n, TICK_PARAM_REQUIRES(tick::trait<is_incrementable>(it))) { while (n--) ++it; } );

Previously in Fit, if the user called advance incorrectly(like advance(foo()) ), clang would report an error like this:

overloading-1.cpp:227:5: error: no matching function for call to object of type 'const fit::detail::static_function_wrapper<fit::conditional_adaptor<<lambda at overloading-1.cpp:179:5>, <lambda at overloading-1.cpp:183:5>, <lambda at overloading-1.cpp:192:5> > >' advance(foo(), 1); ^~~~~~~ ../../../github/Fit/fit/function.h:68:10: note: candidate template ignored: substitution failure [with Ts = <foo, int>]: no matching function for call to object of type 'const fit::conditional_adaptor<<lambda at overloading-1.cpp:179:5>, <lambda at overloading-1.cpp:183:5>, <lambda at overloading-1.cpp:192:5> >' auto operator()(Ts&&... xs) const FIT_RETURNS ^

Even though the error may not be long, it contains very little useful information. Since then, however, Fit has improved its error reporting to this:

overloading-1.cpp:227:5: error: no matching function for call to object of type 'const fit::reveal_adaptor<fit::detail::static_function_wrapper<fit::conditional_adaptor<<lambda at overloading-1.cpp:179:5>, <lambda at overloading-1.cpp:183:5>, <lambda at overloading-1.cpp:192:5> > > >' advance(foo(), 1); ^~~~~~~ ../../../github/Fit/fit/reveal.h:117:20: note: candidate template ignored: substitution failure [with Ts = <foo, int>, $1 = void]: no matching function for call to object of type '<lambda at overloading-1.cpp:179:5>' constexpr auto operator()(Ts&&... xs) -> ^ ../../../github/Fit/fit/reveal.h:117:20: note: candidate template ignored: substitution failure [with Ts = <foo, int>, $1 = void]: no matching function for call to object of type '<lambda at overloading-1.cpp:183:5>' constexpr auto operator()(Ts&&... xs) -> ^ ../../../github/Fit/fit/reveal.h:117:20: note: candidate template ignored: substitution failure [with Ts = <foo, int>, $1 = void]: no matching function for call to object of type '<lambda at overloading-1.cpp:192:5>' constexpr auto operator()(Ts&&... xs) -> ^ ../../../github/Fit/fit/function.h:67:10: note: candidate template ignored: substitution failure [with Ts = <foo, int>]: no matching function for call to object of type 'const fit::conditional_adaptor<<lambda at overloading-1.cpp:179:5>, <lambda at overloading-1.cpp:183:5>, <lambda at overloading-1.cpp:192:5> >' auto operator()(Ts&&... xs) const FIT_RETURNS ^

So there is a note now for each overload(such as substitution failure [with Ts = <foo, int>, $1 = void]: no matching function for call to object of type '<lambda at overloading-1.cpp:179:5>' ). We can now kind of see which functions can’t be called, but we still can’t see why the compiler couldn’t call it.

Giving Clang a nudge

Improving these error messages, has been reported as a bug almost 3 years ago. There are a different opinions on the best way to solve this issue. As such, its still left unresolved. I won’t go into all the details about the bug(if you want more info you can read about it here). However, there is a patch here, that can be used on the latest clang to help us improve these errors. It doesn’t completely resolve the original bug report, but it is a step in the right direction for simple cases.

Using the patch for the previous example we now get an error like this:

overloading-1.cpp:227:5: error: no matching function for call to object of type 'const fit::reveal_adaptor<fit::detail::static_function_wrapper<fit::conditional_adaptor<(lambda at overloading-1.cpp:179:5), (lambda at overloading-1.cpp:183:5), (lambda at overloading-1.cpp:192:5)> > >' advance(foo(), 1); ^~~~~~~ overloading-1.cpp:179:25: note: candidate template ignored: disabled by 'enable_if' [with $0 = foo] [](auto& it, int n, TICK_PARAM_REQUIRES(tick::trait<is_advanceable>(it, n))) ^ /home/paul/github/Tick/tick/requires.h:62:5: note: expanded from macro 'TICK_PARAM_REQUIRES' (tick::detail::param_extract<decltype(__VA_ARGS__)>::value), \ ^ overloading-1.cpp:183:25: note: candidate template ignored: disabled by 'enable_if' [with $0 = foo] [](auto& it, int n, TICK_PARAM_REQUIRES(tick::trait<is_decrementable>(it))) ^ /home/paul/github/Tick/tick/requires.h:62:5: note: expanded from macro 'TICK_PARAM_REQUIRES' (tick::detail::param_extract<decltype(__VA_ARGS__)>::value), \ ^ overloading-1.cpp:192:25: note: candidate template ignored: disabled by 'enable_if' [with $0 = foo] [](auto& it, int n, TICK_PARAM_REQUIRES(tick::trait<is_incrementable>(it))) ^ /home/paul/github/Tick/tick/requires.h:62:5: note: expanded from macro 'TICK_PARAM_REQUIRES' (tick::detail::param_extract<decltype(__VA_ARGS__)>::value), \ ^ overloading-1.cpp:192:25: note: candidate template ignored: disabled by 'enable_if' [with $0 = foo] /home/paul/github/Tick/tick/requires.h:62:5: note: expanded from macro 'TICK_PARAM_REQUIRES' (tick::detail::param_extract<decltype(__VA_ARGS__)>::value), \ ^

Instead of just saying the function can’t be called, it tells us why each function can’t be called(like disabled by 'enable_if' [with $0 = foo] ). Of course, the anonymous types from the generic lambda (ie $0 = foo ) are not the easiest to see which parameter they are associated with(ie it or n ). This can be improved by rewriting it to use function objects instead of lambdas:

struct advance_advanceable { template<class Iterator, TICK_REQUIRES(is_advanceable<Iterator, int>())> void operator()(Iterator& it, int n) const { it += n; } }; struct advance_decrementable { template<class Iterator, TICK_REQUIRES(is_decrementable<Iterator>())> void operator()(Iterator& it, int n) const { if (n > 0) while (n--) ++it; else { n *= -1; while (n--) --it; } } }; struct advance_incrementable { template<class Iterator, TICK_REQUIRES(is_incrementable<Iterator>())> void operator()(Iterator& it, int n) const { while (n--) ++it; } }; using advance_fn = fit::reveal_adaptor<fit::conditional_adaptor< advance_advanceable, advance_decrementable, advance_incrementable>>; constexpr const advance_fn advance = {};

So now it will report an error like this:

overloading-1.cpp:266:5: error: no matching function for call to object of type 'const advance_fn' (aka 'const reveal_adaptor<fit::conditional_adaptor<advance_advanceable, advance_decrementable, advance_incrementable> >') advance(foo(), 1); ^~~~~~~ overloading-1.cpp:180:30: note: candidate template ignored: disabled by 'enable_if' [with Iterator = foo, TickPrivateBool__LINE__ = true] template<class Iterator, TICK_REQUIRES(is_advanceable<Iterator, int>())> ^ /home/paul/github/Tick/tick/requires.h:56:91: note: expanded from macro 'TICK_REQUIRES' #define TICK_REQUIRES(...) bool TickPrivateBool ## __LINE__=true, typename std::enable_if<(TickPrivateBool##__LINE__ && __VA_ARGS__), int>::type = 0 ^ overloading-1.cpp:189:30: note: candidate template ignored: disabled by 'enable_if' [with Iterator = foo, TickPrivateBool__LINE__ = true] template<class Iterator, TICK_REQUIRES(is_decrementable<Iterator>())> ^ /home/paul/github/Tick/tick/requires.h:56:91: note: expanded from macro 'TICK_REQUIRES' #define TICK_REQUIRES(...) bool TickPrivateBool ## __LINE__=true, typename std::enable_if<(TickPrivateBool##__LINE__ && __VA_ARGS__), int>::type = 0 ^ overloading-1.cpp:203:30: note: candidate template ignored: disabled by 'enable_if' [with Iterator = foo, TickPrivateBool__LINE__ = true] template<class Iterator, TICK_REQUIRES(is_incrementable<Iterator>())> ^ /home/paul/github/Tick/tick/requires.h:56:91: note: expanded from macro 'TICK_REQUIRES' #define TICK_REQUIRES(...) bool TickPrivateBool ## __LINE__=true, typename std::enable_if<(TickPrivateBool##__LINE__ && __VA_ARGS__), int>::type = 0 ^ overloading-1.cpp:203:30: note: candidate template ignored: disabled by 'enable_if' [with Iterator = foo, TickPrivateBool__LINE__ = true] /home/paul/github/Tick/tick/requires.h:56:91: note: expanded from macro 'TICK_REQUIRES' #define TICK_REQUIRES(...) bool TickPrivateBool ## __LINE__=true, typename std::enable_if<(TickPrivateBool##__LINE__ && __VA_ARGS__), int>::type = 0 ^

Also, the object type is easier to read(ie const advance_fn ) instead of a lambda.

Recursive print

Now, let’s look at how these error messages appear when we use the recursive print , as defined in a previous post:

FIT_STATIC_LAMBDA_FUNCTION(print) = fit::fix(fit::conditional( [](auto, const auto& x) -> decltype(std::cout << x, void()) { std::cout << x << std::endl; }, [](auto self, const auto& range) -> decltype(self(*adl::adl_begin(range)), void()) { for(const auto& x:range) self(x); }, [](auto self, const auto& tuple) -> decltype(for_each_tuple(tuple, self), void()) { return for_each_tuple(tuple, self); } ));

Now with this function we don’t specify explicit type requirements, but instead rely on whether certain expressions are valid. So trying to do print(foo()) , produces this error:

print.cpp:88:5: error: no matching function for call to object of type 'const fit::reveal_adaptor<fit::detail::static_function_wrapper<fit::fix_adaptor<fit::conditional_adaptor<(lambda at print.cpp:53:5), (lambda at print.cpp:57:5), (lambda at print.cpp:61:5)> > > >' print(foo()); ^~~~~ print.cpp:53:51: note: candidate template ignored: substitution failure [with $0 = fit::fix_adaptor<fit::conditional_adaptor<(lambda at print.cpp:53:5), (lambda at print.cpp:57:5), (lambda at print.cpp:61:5)> >, $1 = foo]: invalid operands to binary expression ('ostream' (aka 'basic_ostream<char>') and 'const foo') [](auto, const auto& x) -> decltype(std::cout << x, void()) ^~ print.cpp:25:35: note: candidate template ignored: substitution failure [with R = const foo &]: no matching function for call to 'begin' auto adl_begin(R&& r) -> decltype(begin(r)); ^~~~~ print.cpp:61:50: note: candidate template ignored: substitution failure [with $0 = fit::fix_adaptor<fit::conditional_adaptor<(lambda at print.cpp:53:5), (lambda at print.cpp:57:5), (lambda at print.cpp:61:5)> >, $1 = foo]: no matching function for call to object of type 'const fit::reveal_adaptor<fit::detail::static_function_wrapper<(lambda at print.cpp:31:39)> >' [](auto self, const auto& tuple) -> decltype(for_each_tuple(tuple, self), void()) ^~~~~~~~~~~~~~ print.cpp:61:50: note: candidate template ignored: substitution failure [with $0 = fit::fix_adaptor<fit::conditional_adaptor<(lambda at print.cpp:53:5), (lambda at print.cpp:57:5), (lambda at print.cpp:61:5)> >, $1 = foo]: no matching function for call to object of type 'const fit::reveal_adaptor<fit::detail::static_function_wrapper<(lambda at print.cpp:31:39)> >' [](auto self, const auto& tuple) -> decltype(for_each_tuple(tuple, self), void()) ^~~~~~~~~~~~~~

So now this reports each expression that is not valid. The fix combinator does make it a little nosier, but it still has all the important information for the user to diagnosis why they can’t call the function.

Futher improvements