简体   繁体   中英

What normative rules govern and allow SFINAE?

SFINAE, "Substitutation Failure Is Not An Error", is a well-known rule/technique applied during overload resolution of function templates (see eg SFINAE @ cppreference ). However, it is never referred to by that name in the C++ standard; the only reference I can find is that of an implementation-dependent macro from <version> , as specified in [version.syn]/2

#define __cpp_lib_result_of_sfinae 201210L // also in <functional>, <type_traits>

What is(/are) the normative reference(s) for this rule/technique?

Let's start from an unqualified function call ( f(1) below), particularly in an example constructed such that the postfix-expression of the function call names two function templates, one of which we expect to be rejected by SFINAE.

#include <type_traits>

// A: expected to be viable
template<typename T, std::enable_if_t<std::is_integral_v<T>>* = nullptr>
constexpr bool f(T) { return true; }

// B: expected to be rejected
template<typename T, std::enable_if_t<!std::is_integral_v<T>>* = nullptr>
constexpr bool f(T) { return false; }

static_assert(f(1));

The function call above will, as per [over.match.call.general]/1 , result in overload resolution applied as specified in [over.call.func] , particularly [over.call.func]/3 :

[over.match.call.general]/1 In a function call

postfix-expression ( expression-list_opt )

if the postfix-expression names at least one function or function template, overload resolution is applied as specified in [over.call.func].

[over.call.func]/3 [...] The name is looked up in the context of the function call following the normal rules for name lookup in expressions ([basic.lookup]). The function declarations found by that lookup constitute the set of candidate functions. [...]

[over.match.general]/2 decribes the high-level steps of overload resolution:

Overload resolution selects the function to call in seven distinct contexts within the language:

  • (2.1) invocation of a function named in the function call syntax;
  • [...]

Each of these contexts defines the set of candidate functions and the list of arguments in its own unique way. But, once the candidate functions and argument lists have been identified, the selection of the best function is the same in all cases :

  • [...]

Telling us that selecting a function during overload resolution works in three different phases:

  1. Identify the candidate functions and associated argument lists
  2. From these, select the subset of viable functions
  3. From these, select the best viable function based on ranking via implicit conversion sequences matching arguments with parameters

SFINAE works in the domain of 1 whilst identifying candidate functions, not so much by rejecting potential candidates but rather by failing to generate a potential candidate when such a potential candidate is a function template (specialization), particularly failing due to template deduction failures. Our example is in the context of invocation of a function named in the function call syntax, but for the context of finding the normative rules that governs SFINAE, it is representable also for other contexts as listed by [over.match.general]/2.

[over.match.funcs] , particularly [over.match.funcs.general]/7 tells us that we enter the domain of template argument deduction for candidates that are function templates:

In each case where a candidate is a function template, candidate function template specializations are generated using template argument deduction ([temp.over], [temp.deduct]) [...] Those candidates are then handled as candidate functions in the usual way.

[temp.over] covers overload resolution rules specific to function templates and [temp.over]/1 , in particular, contains one of two essential rules which govern SFINAE:

When a call to the name of a function or function template is written [...] template argument deduction [...] [is] performed [...] if the argument deduction and checking succeeds [...] [the] function template specialization [...] is added to the candidate functions set to be used in overload resolution. If , for a given function template, argument deduction fails or the synthesized function template specialization would be ill-formed, no such function is added to the set of candidate functions for that template .

Namely that a template argument deduction failure in the phase of finding candidate functions during overload resolution results in the function not being included in the set of candidates.

[temp.deduct] covers template argument deduction, and includes several ways in which it can fail, eg [temp.deduct]/2 , /5 , and /7 , but the second essential rule which governs SFINAE is particularly found in [temp.deduct]/8 :

/7 The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. [...] The substitution proceeds in lexical order and stops when a condition that causes deduction to fail is encountered. [...]

/8 If a substitution results in an invalid type or expression, type deduction fails . An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written using the substituted arguments.

[...]

Only invalid types and expressions in the immediate context of the function type, its template parameter types, and its explicit-specifier can result in a deduction failure.

The latter paragraph of [temp.deduct]/8 is the reason for some notoriously tricky non-SFINAE hard failures when using generic lambdas in return type deduction. See eg P0238R1 for details.

If we return to the function call f(1) in the example above, the function template B will be rejected as a candidate as template argument deduction fails when replacing T in std::enable_if_t<:std::is_integral_v<T>>* , namely in the 2nd type template parameter of the template-head, with substitution of the deduced T ( int ) resulting in an invalid type ( std::enable_if_t<:std::is_integral_v<int>>* ).

[temp.deduct]/5 When all template arguments have been deduced or obtained from default template arguments, all uses of template parameters in the template parameter list of the template and the function type are replaced with the corresponding deduced or default argument values . If the substitution results in an invalid type, as described above, type deduction fails.

Finally, [temp.deduct]/5 is also where we find rules for constraints, which are not a SFINAE mechanism, but a separate step where non-satisfaction results in type deduction failure.

/5 [...] If the function template has associated constraints ([temp.constr.decl]), those constraints are checked for satisfaction ([temp.constr.constr]). If the constraints are not satisfied, type deduction fails.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM