This is an advanced blog post more geared to library writers who want to improve error messages due to substitution failure. It discuss how substitution failures can be transported so the correct information can be presented to the user. Lets first look at the problem.

The problem

A couple years ago Eric Niebler posted on boost mailing C++11 decltype/SFINAE puzzler. The problem was getting a useful error message when using a trailing decltype. Here’s a simple example:

#define REQUIRES(...) typename std::enable_if<(__VA_ARGS__), int>::type = 0

struct h_f 
{
    template<class T, REQUIRES(std::is_pointer<T>())>
    auto operator()(T x) const -> decltype(*x) { return *x; }
};
constexpr static h_f h = {};

struct g_f 
{
    template<class T>
    auto operator()(T x) const -> decltype(h(x)) { return h(x); }
};
constexpr static g_f g = {};

struct f_f 
{
    template<class T>
    auto operator()(T x) const -> decltype(g(x)) { return g(x); };
};
constexpr static f_f f = {};

int main()
{
    f(0);
}

So f calls g that then calls h which will finally dereference the variable(the functions f, g, and h could be abstractions in a library). However, an integer can’t be dereferenced so we get an error message like this:

reveal1.cpp:126:5: error: no matching function for call to object of type 'const f_f'
    f(0);
    ^
reveal1.cpp:120:10: note: candidate template ignored: substitution failure [with T = int]: no matching function for call to object of type 'const g_f'
    auto operator()(T x) const -> decltype(g(x)) { return g(x); };

This tells where we had the mistake, but it doesn’t tell us why. It just tells us that g can’t be called, which isn’t useful. Of course, one solution is to convert the substitution failures to errors, however, this solution still has its problems as well, such as, more compiler errors, and loss of template constraints. Ideally, we would like to preserve where the error occurs, but instead report back a more informative note.

The solution

So, we can use template aliases to transport the substitution failures. In each function object we will put a nested failure struct. We can write a trait to retrieve this nested failure struct when it exists:

template<class F, class=void>
struct get_failure;

template<class F>
struct get_failure<F, typename always_void< 
    typename F::failure
>::type>
: F::failure
{};

So if the function object doesn’t have a nested failure we can provide a default using decltype. Let’s try something like this:

template<class F, class=void>
struct get_failure
{
    template<class... Ts>
    using of = decltype(std::declval<F>()(std::declval<Ts>()...));
};

However, this is not very flexible if we want the user to overwrite this behavior since template aliases can’t be specialized. However, making it a nested type like this won’t work either:

template<class F, class=void>
struct get_failure
{
    template<class... Ts>
    struct of
    {
        using type = decltype(std::declval<F>()(std::declval<Ts>()...));
    };
};

Since, that is no longer substitution failure(and no longer a template alias). Instead, we want of to have nested template alias. But what do we pass in for the template parameter? Well, the identity function object, which we can use to delay possible compile errors:

struct identity
{
    template<class T>
    constexpr T operator()(T&& x) const
    {
        return std::forward<T>(x);
    }
};

template<class F, class=void>
struct get_failure
{
    template<class... Ts>
    struct of
    {
        template<class Id>
        using apply = decltype(Id()(std::declval<F>())(std::declval<Ts>()...));
    };
};

Ok so now we have a way to write the failure struct, lets look at writing a function adaptor that will take the function, and show the substitution failure:

// Workaround for some compilers
template<class Failure, class... Ts>
struct apply_failure
: Failure::template of<Ts...>
{};

// A template alias to enable the function
template<class Id, class Failure, class... Ts>
using enabled = typename apply_failure<Failure, Ts...>::template apply<Id>;

template<class F, class Failure=get_failure<F>>
struct reveal_failure
{
    
    // identity is passed in as a default template parameter so the
    // subtitution failure is delayed until the function is called
    template<class... Ts, class Id=identity, class=enabled<Id, Failure, Ts...>>
    auto operator()(Ts&&... xs) const -> decltype(F()(std::forward<Ts>(xs)...))
    {
        return F()(std::forward<Ts>(xs)...);
    }
};

Ok, so now we can write the functions with nested failure structs. We will use a REQUIRE_OF macro to help write requirements:

#define REQUIRE_OF(...) template<class Id> using apply = typename std::enable_if<(Id()(__VA_ARGS__)), int>::type

struct h_f 
{
    struct failure
    {
        template<class... Ts>
        struct of;

        template<class T>
        struct of<T>
        {
            REQUIRE_OF(std::is_pointer<T>());
        };
    };
    template<class T, REQUIRES(std::is_pointer<T>())>
    auto operator()(T x) const -> decltype(*x) { return *x; }
};
constexpr static h_f h = {};

struct g_f 
{
    struct failure
    : get_failure<h_f>
    {};
    template<class T>
    auto operator()(T x) const -> decltype(h(x)) { return h(x); }
};
constexpr static g_f g = {};

struct f_f 
{
    struct failure
    : get_failure<g_f>
    {};
    template<class T>
    auto operator()(T x) const -> decltype(g(x)) { return g(x); };
};
constexpr static reveal_failure<f_f> f = {};

Ok so now when we call f(0) we get an error message like this:

reveal1.cpp:102:5: error: no matching function for call to object of type 'const reveal_failure<f_f>'
    f(0);
    ^
reveal1.cpp:72:13: note: candidate template ignored: disabled by 'enable_if' [with Ts = <int>, Id = identity]
            REQUIRE_OF(std::is_pointer<T>());
            ^
reveal1.cpp:9:82: note: expanded from macro 'REQUIRE_OF'
#define REQUIRE_OF(...) template<class Id> using apply = typename std::enable_if<(Id()(__VA_ARGS__)), int>::type
                                                                                 ^

And now we can see a little better that this fails because we aren’t passing in a pointer.

Multiple overloads

Now, the above works when there is a single overload. However, sometimes we want to transport multiple substitution failures from different overloads. To do this we just have to create multiple overloads, however, we don’t want to create an overload for each failure. As example, when we do conditional overloading we use basic_conditional which could call other nested conditionals:

template<class F1, class F2>
struct basic_conditional
{
    template<class... Ts>
    auto operator()(Ts&&... xs) -> decltype(F1()(std::forward<Ts>(xs)...))
    {
        return F1()(std::forward<Ts>(xs)...);
    }

    template<class... Ts, REQUIRES(!models<Callable(F1, Ts&&...)>())>
    auto operator()(Ts&&... xs) -> decltype(F2()(std::forward<Ts>(xs)...))
    {
        return F2()(std::forward<Ts>(xs)...);
    }
};

template<class F, class... Fs>
struct conditional : basic_conditional<F, conditional<Fs...>>
{};

template<class F>
struct conditional<F> : F
{};

We don’t want to report the failures for basic_conditional or conditional, just for the “leafs”. So in basic_conditional we report back the “children” failures:

template<class F1, class F2>
struct basic_conditional
{
    struct failure
    {
        using children = failures<get_failure<F1>, get_failure<F2>>;
    };
    template<class... Ts>
    auto operator()(Ts&&... xs) -> decltype(F1()(std::forward<Ts>(xs)...))
    {
        return F1()(std::forward<Ts>(xs)...);
    }

    template<class... Ts, REQUIRES(!models<Callable(F1, Ts&&...)>())>
    auto operator()(Ts&&... xs) -> decltype(F2()(std::forward<Ts>(xs)...))
    {
        return F2()(std::forward<Ts>(xs)...);
    }
};

So now we just need to traverse the tree and only use reveal_failure when there is no children:

// If there is no children then make one overload for the failure
template<class F, class Failure=get_failure<F>, class=void>
struct traverse_failure 
: reveal_failure<F, Failure>
{};

// If there is children then create the overloads for the child failures
template<class F, class Failure>
struct traverse_failure<F, Failure, typename always_void< 
    typename Failure::children
>::type> 
: Failure::children::template apply<F>
{};

template<class Failure, class... Failures>
struct failures 
{
    // Traverse first failure, and then traverse the tail.
    template<class F>
    struct apply
    : traverse_failure<F, Failure>, failures<Failures...>::template apply<F>
    {
        using traverse_failure<F, Failure>::operator();
        using failures<Failures...>::template apply<F>::operator();
        
    };
};

// Base case of just one child failure
template<class Failure>
struct failures<Failure>
{
    template<class F>
    struct apply
    : traverse_failure<F, Failure>
    {};
};

So now we can traverse the failures and make the overloads. Lets create a reveal adaptor that combines this together, but first we want to change the reveal_failure adaptor to be always disabled in order to avoid ambiguous overloads(since the overload is decided be the condtional adaptor):

template<class F, class Failure>
struct reveal_failure
{
    template<class... Ts, class Id=identity, class=enabled<Id, Failure, Ts...>, REQUIRES(Id()(false))>
    auto operator()(Ts&&... xs) -> decltype(F()(std::forward<Ts>(xs)...))
    {
        return F()(std::forward<Ts>(xs)...);
    }
};

Then reveal will traverse the failures, and it will add in the original function:

template<class F>
struct reveal
: traverse_failure<F>, F
{
    using traverse_failure<F>::operator();
    using F::operator();
};

So if we use reveal with the advance function(defined in this post), like this:

constexpr static reveal<conditional<advance_advanceable, advance_decrementable, advance_incrementable>> advance = {};

struct foo {};

int main()
{
    foo f;
    advance(f, 1);
}

The compiler will report back an error like this:

reveal2.cpp:288:5: error: no matching function for call to object of type 'reveal<conditional<advance_advanceable, advance_decrementable, advance_incrementable> >'
    advance(f, 1);
    ^~~~~~~
reveal2.cpp:101:10: note: candidate template ignored: substitution failure [with Ts = <foo &, int>, Id = identity]: no matching function for call to object of type 'advance_advanceable'
    auto operator()(Ts&&... xs) -> decltype(F()(std::forward<Ts>(xs)...))
         ^
reveal2.cpp:101:10: note: candidate template ignored: substitution failure [with Ts = <foo &, int>, Id = identity]: no matching function for call to object of type 'advance_decrementable'
    auto operator()(Ts&&... xs) -> decltype(F()(std::forward<Ts>(xs)...))
         ^
reveal2.cpp:101:10: note: candidate template ignored: substitution failure [with Ts = <foo &, int>, Id = identity]: no matching function for call to object of type 'conditional<advance_incrementable>'
    auto operator()(Ts&&... xs) -> decltype(F()(std::forward<Ts>(xs)...))
         ^
reveal2.cpp:168:10: note: candidate template ignored: substitution failure [with Ts = <foo &, int>]: no matching function for call to object of type 'advance_advanceable'
    auto operator()(Ts&&... xs) -> decltype(F1()(std::forward<Ts>(xs)...))
         ^                                  ~~
reveal2.cpp:174:10: note: candidate template ignored: substitution failure [with Ts = <foo &, int>, $1 = 0]: no matching function for call to object of type
      'conditional<advance_decrementable, advance_incrementable>'
    auto operator()(Ts&&... xs) -> decltype(F2()(std::forward<Ts>(xs)...))
         ^

But we can add the type requirements to each function object to improve the error reporting:

struct advance_advanceable
{
    struct failure
    {
        template<class...>
        struct of;
        template<class Iterator, class T>
        struct of<Iterator, T>
        {
            REQUIRE_OF(models<Advanceable(Iterator, int)>());
        };
    };
    template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())>
    void operator()(Iterator& it, int n) const
    {
        it += n;
    }
};

struct advance_decrementable
{
    struct failure
    {
        template<class...>
        struct of;
        template<class Iterator, class T>
        struct of<Iterator, T>
        {
            REQUIRE_OF(models<Decrementable(Iterator)>());
        };
    };
    template<class Iterator, REQUIRES(models<Decrementable(Iterator)>())>
    void operator()(Iterator& it, int n) const
    {
        if (n > 0) while (n--) ++it;
        else 
        {
            n *= -1;
            while (n--) --it;
        }
    }
};

struct advance_incrementable
{
    struct failure
    {
        template<class...>
        struct of;
        template<class Iterator, class T>
        struct of<Iterator, T>
        {
            REQUIRE_OF(models<Incrementable(Iterator)>());
        };
    };
    template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())>
    void operator()(Iterator& it, int n) const
    {
        while (n--) ++it;
    }
};

So now the compiler reports back this:

reveal2.cpp:288:5: error: no matching function for call to object of type 'reveal<conditional<advance_advanceable, advance_decrementable, advance_incrementable> >'
    advance(f, 1);
    ^~~~~~~
reveal2.cpp:215:13: note: candidate template ignored: disabled by 'enable_if' [with Ts = <foo &, int>, Id = identity]
            REQUIRE_OF(models<Advanceable(Iterator, int)>());
            ^
reveal2.cpp:7:82: note: expanded from macro 'REQUIRE_OF'
#define REQUIRE_OF(...) template<class Id> using apply = typename std::enable_if<(Id()(__VA_ARGS__)), int>::type
                                                                                 ^
reveal2.cpp:234:13: note: candidate template ignored: disabled by 'enable_if' [with Ts = <foo &, int>, Id = identity]
            REQUIRE_OF(models<Decrementable(Iterator)>());
            ^
reveal2.cpp:7:82: note: expanded from macro 'REQUIRE_OF'
#define REQUIRE_OF(...) template<class Id> using apply = typename std::enable_if<(Id()(__VA_ARGS__)), int>::type
                                                                                 ^
reveal2.cpp:258:13: note: candidate template ignored: disabled by 'enable_if' [with Ts = <foo &, int>, Id = identity]
            REQUIRE_OF(models<Incrementable(Iterator)>());
            ^
reveal2.cpp:7:82: note: expanded from macro 'REQUIRE_OF'
#define REQUIRE_OF(...) template<class Id> using apply = typename std::enable_if<(Id()(__VA_ARGS__)), int>::type
                                                                                 ^
reveal2.cpp:168:10: note: candidate template ignored: substitution failure [with Ts = <foo &, int>]: no matching function for call to object of type 'advance_advanceable'
    auto operator()(Ts&&... xs) -> decltype(F1()(std::forward<Ts>(xs)...))
         ^                                  ~~
reveal2.cpp:174:10: note: candidate template ignored: substitution failure [with Ts = <foo &, int>, $1 = 0]: no matching function for call to object of type
      'conditional<advance_decrementable, advance_incrementable>'
    auto operator()(Ts&&... xs) -> decltype(F2()(std::forward<Ts>(xs)...))
         ^     

So we can see the type requirements in each overload.

Conclusion

So using this allows a way to improve the reporting of errors, of course, it still has some areas of improvements. First, the type requirements have to be written twice. It would be nice if clang could report back one level deeper in the backtrace. This way the user wouldn’t need to do anything extra to get better error reporting(the library just needs to transport the error). Secondly, when it does report the type requirements, it is not always clear what each type refers to. It would be nice if clang could show this information as well.