Difference between revisions of "cpp/language/sfinae"
(overload resolution example is added) |
m (→Top: extended navbar, fmt.) |
||
(47 intermediate revisions by 21 users not shown) | |||
Line 1: | Line 1: | ||
{{title|SFINAE}} | {{title|SFINAE}} | ||
− | {{cpp/language/templates/navbar}} | + | {{cpp/language/declarations/expressions/templates/navbar}} |
"Substitution Failure Is Not An Error" | "Substitution Failure Is Not An Error" | ||
− | This rule applies during overload resolution of function templates: When {{rlp| | + | This rule applies during overload resolution of function templates: When {{rlp|function template#Template argument substitution|substituting}} the explicitly specified or {{rlp|template argument deduction|deduced type}} for the template parameter fails, the specialization is discarded from the {{rlp|overload resolution|overload set}} instead of causing a compile error. |
This feature is used in template metaprogramming. | This feature is used in template metaprogramming. | ||
Line 18: | Line 18: | ||
* all types used in the function type (which includes return type and the types of all parameters) | * 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 types used in the template parameter declarations | ||
− | + | * all types used in the template argument list of a partial specialization | |
− | {{ | + | {{rrev|since=c++11| |
* all expressions used in the function type | * all expressions used in the function type | ||
* all expressions used in a template parameter declaration | * all expressions used in a template parameter declaration | ||
+ | * all expressions used in the template argument list of a partial specialization | ||
+ | }} | ||
+ | {{rrev|since=c++20| | ||
+ | * all expressions used in the [[cpp/language/explicit|explicit specifier]] | ||
}} | }} | ||
− | |||
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. | 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. | + | Only the failures in the types and expressions in the ''immediate context'' of the function type or its template parameter types {{rev inl|since=c++20|or its [[cpp/language/explicit|explicit specifier]]}} 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. {{rev inl|since=c++20|A {{rlp|lambda|lambda expression}} is not considered part of the immediate context.}} |
{{todo|mini-example where this matters}} | {{todo|mini-example where this matters}} | ||
− | |||
− | |||
Substitution proceeds in lexical order and stops when a failure is encountered. | Substitution proceeds in lexical order and stops when a failure is encountered. | ||
+ | |||
+ | {{rrev|since=c++11| | ||
+ | If there are multiple declarations with different lexical orders (e.g. a function template declared with trailing return type, to be substituted after a parameter, and redeclared with ordinary return type that would be substituted before the parameter), and that would cause template instantiations to occur in a different order or not at all, then the program is ill-formed; no diagnostic required. | ||
+ | }} | ||
{{source|1= | {{source|1= | ||
− | template<typename | + | template<typename A> |
− | struct | + | struct B { using type = typename A::type; }; |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | ===Type SFINAE=== | + | template< |
+ | class T, | ||
+ | class U = typename T::type, // SFINAE failure if T has no member type | ||
+ | class V = typename B<T>::type> // hard error if B has no member type | ||
+ | // (guaranteed to not occur via CWG 1227 because | ||
+ | // substitution into the default template argument | ||
+ | // of U would fail first) | ||
+ | void foo (int); | ||
+ | |||
+ | template<class T> | ||
+ | typename T::type h(typename B<T>::type); | ||
+ | |||
+ | template<class T> | ||
+ | auto h(typename B<T>::type) -> typename T::type; // redeclaration | ||
+ | |||
+ | template<class T> | ||
+ | void h(...) {} | ||
+ | |||
+ | using R = decltype(h<int>(0)); // ill-formed, no diagnostic required | ||
+ | }} | ||
+ | |||
+ | ====Type SFINAE==== | ||
The following type errors are SFINAE errors: | The following type errors are SFINAE errors: | ||
− | {{ | + | {{rrev|since=c++11| |
− | + | ||
* attempting to instantiate a pack expansion containing multiple packs of different lengths | * attempting to instantiate a pack expansion containing multiple packs of different lengths | ||
}} | }} | ||
− | + | * attempting to create an array of void, array of reference, array of function, array of negative size, array of non-integral size, or array of size zero: | |
− | * attempting to create an array of void, array of reference, array of function, array of | + | |
{{source|1= | {{source|1= | ||
− | template<int I> void div(char(*)[I % 2 == 0] = | + | template<int I> |
+ | void div(char(*)[I % 2 == 0] = nullptr) | ||
+ | { | ||
// this overload is selected when I is even | // this overload is selected when I is even | ||
} | } | ||
− | template<int I> void div(char(*)[I % 2 == 1] = | + | |
− | // this overload is selected | + | template<int I> |
+ | void div(char(*)[I % 2 == 1] = nullptr) | ||
+ | { | ||
+ | // this overload is selected when I is odd | ||
} | } | ||
}} | }} | ||
− | * attempting to use a type | + | * attempting to use a type on the left of a scope resolution operator {{ttb|::}} and it is not a class or enumeration: |
{{source|1= | {{source|1= | ||
− | template <class T> int f(typename T::B*); | + | template<class T> |
− | template <class T> int f(T); | + | int f(typename T::B*); |
+ | |||
+ | template<class T> | ||
+ | int f(T); | ||
+ | |||
int i = f<int>(0); // uses second overload | int i = f<int>(0); // uses second overload | ||
}} | }} | ||
Line 73: | Line 99: | ||
:* the specified member is not a non-type where a non-type is required | :* the specified member is not a non-type where a non-type is required | ||
{{source|1= | {{source|1= | ||
− | template <int I> struct X { }; | + | template<int I> |
− | template <template <class T> class> struct Z { }; | + | struct X {}; |
− | template <class T> void f(typename T::Y*){} | + | |
− | template <class T> void g(X<T::N>*){} | + | template<template<class T> class> |
− | template <class T> void h(Z<T::template TT>*){} | + | 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 A {}; | ||
struct B { int Y; }; | struct B { int Y; }; | ||
struct C { typedef int N; }; | struct C { typedef int N; }; | ||
struct D { typedef int TT; }; | struct D { typedef int TT; }; | ||
− | int main() { | + | 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: | // Deduction fails in each of these cases: | ||
f<A>(0); // A does not contain a member Y | f<A>(0); // A does not contain a member Y | ||
Line 88: | Line 133: | ||
g<C>(0); // The N member of C is not a non-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 | 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 | // todo: needs to demonstrate overload resolution, not just failure | ||
Line 93: | Line 143: | ||
* attempting to create a pointer to reference | * attempting to create a pointer to reference | ||
* attempting to create a reference to void | * attempting to create a reference to void | ||
− | * attempting to create pointer to member of T, where T is not a class type | + | * attempting to create pointer to member of T, where T is not a class type: |
{{source|1= | {{source|1= | ||
template<typename T> | template<typename T> | ||
− | class is_class { | + | class is_class |
+ | { | ||
typedef char yes[1]; | typedef char yes[1]; | ||
− | typedef char no [2]; | + | typedef char no[2]; |
− | template<typename C> static yes& test(int C::*); // selected if C is a class type | + | |
− | template<typename C> static no& | + | template<typename C> |
− | + | static yes& test(int C::*); // selected if C is a class type | |
− | static bool const value = sizeof(test<T>( | + | |
+ | template<typename C> | ||
+ | static no& test(...); // selected otherwise | ||
+ | public: | ||
+ | static bool const value = sizeof(test<T>(nullptr)) == sizeof(yes); | ||
}; | }; | ||
}} | }} | ||
− | * attempting to give an invalid type to a non-type template parameter | + | * attempting to give an invalid type to a non-type template parameter: |
{{source|1= | {{source|1= | ||
− | template <class T, T> struct S {}; | + | template<class T, T> |
− | template <class T> int f(S<T, T()>*); | + | struct S {}; |
+ | |||
+ | template<class T> | ||
+ | int f(S<T, T()>*); | ||
+ | |||
struct X {}; | struct X {}; | ||
int i0 = f<X>(0); | int i0 = f<X>(0); | ||
Line 115: | Line 174: | ||
* attempting to perform an invalid conversion in | * attempting to perform an invalid conversion in | ||
:* in a template argument expression | :* in a template argument expression | ||
− | :* in an expression used in function declaration | + | :* in an expression used in function declaration: |
{{source|1= | {{source|1= | ||
− | template <class T, T*> int f(int); | + | template<class T, T*> int f(int); |
− | int i2 = f<int,1>(0); // can’t conv 1 to int* | + | int i2 = f<int, 1>(0); // can’t conv 1 to int* |
// todo: needs to demonstrate overload resolution, not just failure | // 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 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 function type which returns an array type or a function type | ||
− | + | <!-- removed by p0929r2 adopted as a DR in Jax-2018 {{rrev |since=c++11| | |
− | + | * attempting to create a function type with a parameter or return type that is an {{rlp|abstract class}} | |
− | + | }}--> | |
− | + | ||
− | * attempting to create a function type with a parameter or return type that is an {{rlp|abstract class}} | + | |
− | }} | + | |
− | ===Expression SFINAE=== | + | ====Expression SFINAE==== |
{{rev begin}} | {{rev begin}} | ||
− | {{rev | since=c++11 | | + | {{rev|until=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. | ||
+ | }} | ||
+ | {{rev|since=c++11| | ||
The following expression errors are SFINAE errors | The following expression errors are SFINAE errors | ||
* Ill-formed expression used in a template parameter type | * Ill-formed expression used in a template parameter type | ||
− | * Ill-formed expression used in the function type | + | * Ill-formed expression used in the function type: |
{{source|1= | {{source|1= | ||
struct X {}; | struct X {}; | ||
struct Y { Y(X){} }; // X is convertible to Y | struct Y { Y(X){} }; // X is convertible to Y | ||
− | template <class T> | + | template<class T> |
auto f(T t1, T t2) -> decltype(t1 + t2); // overload #1 | auto f(T t1, T t2) -> decltype(t1 + t2); // overload #1 | ||
− | X f(Y, Y); | + | X f(Y, Y); // overload #2 |
X x1, x2; | X x1, x2; | ||
− | X x3 = f(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 | |
}} | }} | ||
− | |||
− | |||
− | |||
}} | }} | ||
{{rev end}} | {{rev end}} | ||
+ | |||
+ | ====SFINAE in partial specializations==== | ||
+ | Deduction and substitution also occur while determining whether a specialization of a class {{rev inl|since=c++14|or variable}} template is generated by some {{rlp|partial specialization}} or the primary template. A substitution failure is not treated as a hard-error during such determination, but makes the corresponding partial specialization declaration ignored instead, as if in the overload resolution involving function templates. | ||
+ | |||
+ | {{source|1= | ||
+ | // primary template handles non-referenceable types: | ||
+ | template<class T, class = void> | ||
+ | struct reference_traits | ||
+ | { | ||
+ | using add_lref = T; | ||
+ | using add_rref = T; | ||
+ | }; | ||
+ | |||
+ | // specialization recognizes referenceable types: | ||
+ | template<class T> | ||
+ | struct reference_traits<T, std::void_t<T&>> | ||
+ | { | ||
+ | using add_lref = T&; | ||
+ | using add_rref = T&&; | ||
+ | }; | ||
+ | |||
+ | template<class T> | ||
+ | using add_lvalue_reference_t = typename reference_traits<T>::add_lref; | ||
+ | |||
+ | template<class T> | ||
+ | using add_rvalue_reference_t = typename reference_traits<T>::add_rref; | ||
+ | }} | ||
===Library support=== | ===Library support=== | ||
+ | {{rrev|since=c++11| | ||
The standard library component {{lc|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 {{lc|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. | ||
− | In addition, many [[cpp/ | + | In addition, many {{lsd|cpp/meta#Type traits}} must be implemented with SFINAE if appropriate compiler extensions are unavailable. |
+ | }} | ||
+ | {{rrev|since=c++17| | ||
+ | The standard library component {{lc|std::void_t}} is another utility metafunction that simplifies partial specialization SFINAE applications. | ||
+ | }} | ||
+ | |||
+ | ===Alternatives=== | ||
+ | Where applicable, [[cpp/iterator/iterator tags#Example|tag dispatch]]{{rev inl|since=c++17|, {{rlpt|if#Constexpr_if|if constexpr}}}}{{rev inl|since=c++20|, and {{rlp|constraints|concepts}} }} are usually preferred over use of SFINAE. | ||
+ | |||
+ | {{rrev|since=c++11|{{rlpt|static_assert}} is usually preferred over SFINAE if only a conditional compile time error is wanted.}} | ||
===Examples=== | ===Examples=== | ||
− | {{example|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. | + | {{example |
+ | |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. | ||
|code= | |code= | ||
#include <iostream> | #include <iostream> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | // | + | // This overload is added to the set of overloads if C is |
− | // | + | // a class or reference-to-class type and F is a pointer to member function of C |
template<class C, class F> | template<class C, class F> | ||
auto test(C c, F f) -> decltype((void)(c.*f)(), void()) | auto test(C c, F f) -> decltype((void)(c.*f)(), void()) | ||
{ | { | ||
− | std::cout << " | + | std::cout << "(1) Class/class 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> | template<class C, class F> | ||
auto test(C c, F f) -> decltype((void)((c->*f)()), void()) | auto test(C c, F f) -> decltype((void)((c->*f)()), void()) | ||
{ | { | ||
− | std::cout << "Pointer overload called\n"; | + | std::cout << "(2) Pointer overload called\n"; |
} | } | ||
− | + | // This overload is always in the set of overloads: ellipsis | |
+ | // parameter has the lowest ranking for overload resolution | ||
+ | void test(...) | ||
+ | { | ||
+ | std::cout << "(3) Catch-all overload called\n"; | ||
+ | } | ||
− | int main(){ | + | int main() |
− | + | { | |
− | + | struct X { void f() {} }; | |
− | + | X x; | |
− | + | X& rx = x; | |
+ | test(x, &X::f); // (1) | ||
+ | test(rx, &X::f); // (1), creates a copy of x | ||
+ | test(&x, &X::f); // (2) | ||
+ | test(42, 1337); // (3) | ||
} | } | ||
|output= | |output= | ||
− | + | (1) Class/class reference overload called | |
− | Pointer overload called | + | (1) Class/class reference overload called |
− | Catch-all overload called | + | (2) Pointer overload called |
+ | (3) Catch-all overload called | ||
}} | }} | ||
+ | |||
+ | ===Defect reports=== | ||
+ | {{dr list begin}} | ||
+ | {{dr list item|wg=cwg|dr=295|std=c++98|before=creating cv-qualified function type<br>could result in substitution failure|after=made not failure,<br>discarding cv-qualification}} | ||
+ | {{dr list item|wg=cwg|dr=1227|std=c++98|before=the order of substitution was unspecified|after=same as the lexical order}} | ||
+ | {{dr list item|wg=cwg|dr=2054|std=c++98|before=substitution in partial specializations was not correctly specified|after=specified}} | ||
+ | {{dr list item|wg=cwg|dr=2322|std=c++11|before=declarations in different lexical orders would cause template<br>instantiations to occur in a different order or not at all|after=such case is ill-formed,<br>no diagnostic required}} | ||
+ | {{dr list end}} | ||
+ | |||
+ | {{langlinks|es|ja|ru|zh}} |
Latest revision as of 21:25, 29 December 2023
"Substitution Failure Is Not An Error"
This rule applies during overload resolution of function templates: When substituting the explicitly specified or 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 |
[edit] 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 types used in the template argument list of a partial specialization
|
(since C++11) |
|
(since C++20) |
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 or its explicit specifier(since C++20) 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. A lambda expression is not considered part of the immediate context.(since C++20)
This section is incomplete Reason: mini-example where this matters |
Substitution proceeds in lexical order and stops when a failure is encountered.
If there are multiple declarations with different lexical orders (e.g. a function template declared with trailing return type, to be substituted after a parameter, and redeclared with ordinary return type that would be substituted before the parameter), and that would cause template instantiations to occur in a different order or not at all, then the program is ill-formed; no diagnostic required. |
(since C++11) |
template<typename A> struct B { using type = typename A::type; }; template< class T, class U = typename T::type, // SFINAE failure if T has no member type class V = typename B<T>::type> // hard error if B has no member type // (guaranteed to not occur via CWG 1227 because // substitution into the default template argument // of U would fail first) void foo (int); template<class T> typename T::type h(typename B<T>::type); template<class T> auto h(typename B<T>::type) -> typename T::type; // redeclaration template<class T> void h(...) {} using R = decltype(h<int>(0)); // ill-formed, no diagnostic required
[edit] Type SFINAE
The following type errors are SFINAE errors:
|
(since C++11) |
- attempting to create an array of void, array of reference, array of function, array of negative size, array of non-integral size, or array of size zero:
template<int I> void div(char(*)[I % 2 == 0] = nullptr) { // this overload is selected when I is even } template<int I> void div(char(*)[I % 2 == 1] = nullptr) { // this overload is selected when I is odd }
- attempting to use a type on the left of a scope resolution operator
::
and it is not a class or enumeration:
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>(nullptr)) == 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
[edit] Expression SFINAE
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) |
The following expression errors are SFINAE errors
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) |
[edit] SFINAE in partial specializations
Deduction and substitution also occur while determining whether a specialization of a class or variable(since C++14) template is generated by some partial specialization or the primary template. A substitution failure is not treated as a hard-error during such determination, but makes the corresponding partial specialization declaration ignored instead, as if in the overload resolution involving function templates.
// primary template handles non-referenceable types: template<class T, class = void> struct reference_traits { using add_lref = T; using add_rref = T; }; // specialization recognizes referenceable types: template<class T> struct reference_traits<T, std::void_t<T&>> { using add_lref = T&; using add_rref = T&&; }; template<class T> using add_lvalue_reference_t = typename reference_traits<T>::add_lref; template<class T> using add_rvalue_reference_t = typename reference_traits<T>::add_rref;
[edit] 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. In addition, many type traits must be implemented with SFINAE if appropriate compiler extensions are unavailable. |
(since C++11) |
The standard library component std::void_t is another utility metafunction that simplifies partial specialization SFINAE applications. |
(since C++17) |
[edit] Alternatives
Where applicable, tag dispatch, if constexpr
(since C++17), and concepts (since C++20) are usually preferred over use of SFINAE.
|
(since C++11) |
[edit] 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 added to the set of overloads if C is // a class or 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 << "(1) Class/class 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 << "(2) Pointer overload called\n"; } // This overload is always in the set of overloads: ellipsis // parameter has the lowest ranking for overload resolution void test(...) { std::cout << "(3) Catch-all overload called\n"; } int main() { struct X { void f() {} }; X x; X& rx = x; test(x, &X::f); // (1) test(rx, &X::f); // (1), creates a copy of x test(&x, &X::f); // (2) test(42, 1337); // (3) }
Output:
(1) Class/class reference overload called (1) Class/class reference overload called (2) Pointer overload called (3) Catch-all overload called
[edit] Defect reports
The following behavior-changing defect reports were applied retroactively to previously published C++ standards.
DR | Applied to | Behavior as published | Correct behavior |
---|---|---|---|
CWG 295 | C++98 | creating cv-qualified function type could result in substitution failure |
made not failure, discarding cv-qualification |
CWG 1227 | C++98 | the order of substitution was unspecified | same as the lexical order |
CWG 2054 | C++98 | substitution in partial specializations was not correctly specified | specified |
CWG 2322 | C++11 | declarations in different lexical orders would cause template instantiations to occur in a different order or not at all |
such case is ill-formed, no diagnostic required |