Namespaces
Variants
Views
Actions

Difference between revisions of "cpp/language/sfinae"

From cppreference.com
< cpp‎ | language
m (Uniform whitespace around template keyword)
m (Rename some identifiers so that the example doesn't read as typety-type-type-type)
Line 34: Line 34:
 
Substitution proceeds in lexical order and stops when a failure is encountered.
 
Substitution proceeds in lexical order and stops when a failure is encountered.
 
{{source|1=
 
{{source|1=
template <typename SomeType>
+
template <typename A>
struct inner_type { typedef typename SomeType::type type; };
+
struct B { typedef typename A::type type; };
 +
 
 
template <
 
template <
 
   class T,
 
   class T,
   class  = typename T::type,           // SFINAE failure if T has no member type
+
   class  = typename T::type,     // SFINAE failure if T has no member type
   class U = typename inner_type<T>::type // hard error if T has no member type
+
   class U = typename B<T>::type   // hard error if T has no member type
                                        // (guaranteed to not occur as of C++14)
+
                                  // (guaranteed to not occur as of C++14)
 
> void foo (int);
 
> void foo (int);
 
}}}}{{rev end}}
 
}}}}{{rev end}}

Revision as of 11:16, 17 November 2017

 
 
C++ language
General topics
Flow control
Conditional execution statements
if
Iteration statements (loops)
for
range-for (C++11)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications (until C++17*)
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
explicit (C++11)
static

Special member functions
Templates
Miscellaneous
 
 

"Substitution Failure Is Not An Error"


This rule applies during overload resolution of function templates: When substituting the deduced type for the template parameter fails, the specialization is discarded from the overload set instead of causing a compile error.

This feature is used in template metaprogramming.

Contents

Explanation

Function template parameters are substituted (replaced by template arguments) twice:

  • explicitly specified template arguments are substituted before template argument deduction
  • deduced arguments and the arguments obtained from the defaults are substituted after template argument deduction

Substitution occurs in

  • all types used in the function type (which includes return type and the types of all parameters)
  • all types used in the template parameter declarations
  • all expressions used in the function type
  • all expressions used in a template parameter declaration
(since C++11)

A substitution failure is any situation when the type or expression above would be ill-formed (with a required diagnostic), if written using the substituted arguments.

Only the failures in the types and expressions in the immediate context of the function type or its template parameter types are SFINAE errors. If the evaluation of a substituted type/expression causes a side-effect such as instantiation of some template specialization, generation of an implicitly-defined member function, etc, errors in those side-effects are treated as hard errors.

Substitution proceeds in lexical order and stops when a failure is encountered.

template <typename A>
struct B { typedef typename A::type type; };
 
template <
  class T,
  class   = typename T::type,      // SFINAE failure if T has no member type
  class U = typename B<T>::type    // hard error if T has no member type
                                   // (guaranteed to not occur as of C++14)
> void foo (int);
(since C++14)

Type SFINAE

The following type errors are SFINAE errors:

  • attempting to instantiate a pack expansion containing multiple packs of different lengths
(since C++11)
  • attempting to create an array of void, array of reference, array of function, array of abstract class types, array of negative size, or array of size zero.
template <int I> void div(char(*)[I % 2 == 0] = 0) {
    // this overload is selected when I is even
}
template <int I> void div(char(*)[I % 2 == 1] = 0) {
    // this overload is selected when I is odd
}
  • attempting to use a type that is not a class or enumeration on the left of a scope resolution operator ::
template <class T> int f(typename T::B*);
template <class T> int f(T);
int i = f<int>(0); // uses second overload
  • attempting to use a member of a type, where
  • the type does not contain the specified member
  • the specified member is not a type where a type is required
  • the specified member is not a template where a template is required
  • the specified member is not a non-type where a non-type is required
template <int I> struct X { };
template <template <class T> class> struct Z { };
template <class T> void f(typename T::Y*){}
template <class T> void g(X<T::N>*){}
template <class T> void h(Z<T::template TT>*){}
struct A {};
struct B { int Y; };
struct C { typedef int N; };
struct D { typedef int TT; };
struct B1 { typedef int Y; };
struct C1 { static const int N = 0; };
struct D1 { 
    template <typename T>
    struct TT
    {    
    }; 
};
int main() {
    // Deduction fails in each of these cases:
    f<A>(0); // A does not contain a member Y
    f<B>(0); // The Y member of B is not a type
    g<C>(0); // The N member of C is not a non-type
    h<D>(0); // The TT member of D is not a template
 
    // Deduction succeeds in each of these cases:
    f<B1>(0); 
    g<C1>(0); 
    h<D1>(0);
}
// todo: needs to demonstrate overload resolution, not just failure
  • attempting to create a pointer to reference
  • attempting to create a reference to void
  • attempting to create pointer to member of T, where T is not a class type
template<typename T>
class is_class {
    typedef char yes[1];
    typedef char no [2];
    template<typename C> static yes& test(int C::*); // selected if C is a class type
    template<typename C> static no&  test(...);      // selected otherwise
  public:
    static bool const value = sizeof(test<T>(0)) == sizeof(yes);
};
  • attempting to give an invalid type to a non-type template parameter
template <class T, T> struct S {};
template <class T> int f(S<T, T()>*);
struct X {};
int i0 = f<X>(0);
// todo: needs to demonstrate overload resolution, not just failure
  • attempting to perform an invalid conversion in
  • in a template argument expression
  • in an expression used in function declaration
template <class T, T*> int f(int);
int i2 = f<int,1>(0); // can’t conv 1 to int*
// todo: needs to demonstrate overload resolution, not just failure
  • attempting to create a function type with a parameter of type void
  • attempting to create a function type which returns an array type or a function type
  • attempting to create a cv-qualified function type
(until C++11)
  • attempting to create a function type with a parameter or return type that is an abstract class.
(since C++11)

Expression SFINAE

The following expression errors are SFINAE errors

  • Ill-formed expression used in a template parameter type
  • Ill-formed expression used in the function type
struct X {};
struct Y { Y(X){} }; // X is convertible to Y
 
template <class T>
auto f(T t1, T t2) -> decltype(t1 + t2); // overload #1
 
X f(Y, Y);  // overload #2
 
X x1, x2;
X x3 = f(x1, x2);  // deduction fails on #1 (expression x1+x2 is ill-formed)
                   // only #2 is in the overload set, and is called
(since C++11)

Only constant expressions that are used in types (such as array bounds) were required to be treated as SFINAE (and not hard errors) before C++11.

(until C++11)

Library support

The standard library component std::enable_if allows for creating a substitution failure in order to enable or disable particular overloads based on a condition evaluated at compile time.

The standard library component std::void_t is another utility metafunction that simplifies SFINAE applications.

In addition, many type traits are implemented using SFINAE.

Alternatives

Where applicable, tag dispatch, static_assert, and, if available, concepts, are usually preferred over direct use of SFINAE.

Examples

A common idiom is to use expression SFINAE on the return type, where the expression uses the comma operator, whose left subexpression is the one that is being examined (cast to void to ensure the user-defined operator comma on the returned type is not selected), and the right subexpression has the type that the function is supposed to return.

#include <iostream>
 
// this overload is always in the set of overloads
// ellipsis parameter has the lowest ranking for overload resolution
void test(...)
{
    std::cout << "Catch-all overload called\n";
}
 
// this overload is added to the set of overloads if
// C is a reference-to-class type and F is a pointer to member function of C
template <class C, class F>
auto test(C c, F f) -> decltype((void)(c.*f)(), void())
{
    std::cout << "Reference overload called\n";
}
 
// this overload is added to the set of overloads if
// C is a pointer-to-class type and F is a pointer to member function of C
template <class C, class F>
auto test(C c, F f) -> decltype((void)((c->*f)()), void())
{
    std::cout << "Pointer overload called\n";
}
 
struct X { void f() {} };
 
int main(){
  X x;
  test( x, &X::f);
  test(&x, &X::f);
  test(42, 1337);
}

Output:

Reference overload called
Pointer overload called
Catch-all overload called