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
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.