Search code examples
c++c++20sfinae

Expression SFINAE and hard errors in c++


I'm confused about why one of these functions gives a hard error, and the other does not:

#include <utility>

template <typename ...Args>
auto ffExists1()
-> decltype(ff(std::declval<Args>()...)); // Attempts to see
    // whether you can call ff (which is not defined or declared)
    // with arguments of type Args... This works fine.

template <typename ...Args>
auto ffExists2() -> decltype(&ff); // Gives me a hard error : ff not defined

Why is that? Also, how do I make ffExists2 work? Taking a pointer to a function would let me determine the exact signature of ff while ffExists1 only finds out if I can call ff with arguments of type Args....

EDIT: here is a version where ffExists2 depends on a template, causing the same result:

#include <utility>

template <typename ...Args>
auto ffExists() -> decltype(ff(std::declval<Args>()...)); //Fine

template <typename ... Args>
void SomeHelperFunc(auto (*) (Args...));

template <typename ...Args>
auto ffExists2() -> decltype(SomeHelperFunc<Args...>(&ff)); // Hard Error

Solution

  • In ffExists2, ff doesn't depend on template parameters, thus the lookup for it (i.e. finding the function by the provided name) is done at phase one of two-phase lookup, i.e. when the compiler first sees the template, as opposed to when the template arguments are substituted into it.

    Because of this, even if ff was defined after this template, it wouldn't matter because phase one is already done by that point.

    On the other hand, in ffExists1, lookup of ff depends on the template parameter (because ADL is a part of this lookup, and ADL requires knowing the types of the parameters, i.e. Args...), so the lookup for it is postponed to phase two, i.e. to the point of instantiation of the template,

    at which time ADL examines function declarations that are visible from the template definition context as well as in the template instantiation context, while non-ADL lookup only examines function declarations that are visible from the template definition context (in other words, adding a new function declaration after template definition does not make it visible except via ADL).

    — (c) cppreference

    There's no way to make &ff depend on a template parameter, and it's impossible to peform SFINAE checks on things that don't depend on template parameters. Any attempt at it would be thwarted by:

    [temp.res.general]/6.1

    The program is ill-formed, no diagnostic required, if:

    — no valid specialization can be generated for a template ...


    You'd think that C++20 requires could help, since you could use it without any templates, but alas:

    [expr.prim.req.general]/5

    ...

    [Note 1: If a requires-expression contains invalid types or expressions in its requirements, and it does not appear within the declaration of a templated entity, then the program is ill-formed. — end note]

    If the substitution of template arguments into a requirement would always result in a substitution failure, the program is ill-formed; no diagnostic required.