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

Of course, there is still more room for improvements. Fit should report more friendly looking types when using lambdas(the FIT_STATIC_LAMBDA_FUNCTION could create a friendly-named class for defining functions). Also, clang could improve the way it reports back parameters from generic lambdas. Instead of reporting the anonymous template parameters(ie $0 = foo), it could report the type of each parameter(ie it = foo&). This is probably more important as regular functions may inherit the auto syntax as well.