What Is "Expression Sfinae"

What is Expression SFINAE ?

Expression SFINAE is explained quite well in the paper you linked, I think. It's SFINAE on expressions. If the expression inside decltype isn't valid, well, kick the function from the VIP lounge of overloads. You can find the normative wording at the end of this answer.

A note on VC++: They didn't implement it completely. On simple expressions, it might work, but on others, it won't. See a discussion in the comments on this answer for examples that fail. To make it simple, this won't work:

#include <iostream>

// catch-all case
void test(...)
{
std::cout << "Couldn't call\n";
}

// catch when C is a reference-to-class type and F is a member function pointer
template<class C, class F>
auto test(C c, F f) -> decltype((c.*f)(), void()) // 'C' is reference type
{
std::cout << "Could call on reference\n";
}

// catch when C is a pointer-to-class type and F is a member function pointer
template<class C, class F>
auto test(C c, F f) -> decltype((c->*f)(), void()) // 'C' is pointer type
{
std::cout << "Could call on pointer\n";
}

struct X{
void f(){}
};

int main(){
X x;
test(x, &X::f);
test(&x, &X::f);
test(42, 1337);
}

With Clang, this outputs the expected:

Could call with reference

Could call with pointer

Couldn't call

With MSVC, I get... well, a compiler error:


1>src\main.cpp(20): error C2995: ''unknown-type' test(C,F)' : function template has already been defined
1> src\main.cpp(11) : see declaration of 'test'

It also seems that GCC 4.7.1 isn't quite up to the task:


source.cpp: In substitution of 'template decltype ((c.*f(), void())) test(C, F) [with C = X*; F = void (X::*)()]':
source.cpp:29:17: required from here
source.cpp:11:6: error: cannot apply member pointer 'f' to 'c', which is of non-class type 'X*'
source.cpp: In substitution of 'template decltype ((c.*f(), void())) test(C, F) [with C = int; F = int]':
source.cpp:30:16: required from here
source.cpp:11:6: error: 'f' cannot be used as a member pointer, since it is of type 'int'

A common use of Expression SFINAE is when defining traits, like a trait to check if a class sports a certain member function:

struct has_member_begin_test{
template<class U>
static auto test(U* p) -> decltype(p->begin(), std::true_type());
template<class>
static auto test(...) -> std::false_type;
};

template<class T>
struct has_member_begin
: decltype(has_member_begin_test::test<T>(0)) {};

Live example. (Which, surprisingly, works again on GCC 4.7.1.)

See also this answer of mine, which uses the same technique in another environment (aka without traits).


Normative wording:

§14.8.2 [temp.deduct]

p6 At certain points in the template argument deduction process it is necessary to take a function type that makes use of template parameters and replace those template parameters with the corresponding template arguments. This is done at the beginning of template argument deduction when any explicitly specified template arguments are substituted into the function type, and again at the end of template argument deduction when any template arguments that were deduced or obtained from default arguments are substituted.

p7 The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (i.e., non-constant expressions) inside sizeof, decltype, and other contexts that allow non-constant expressions.

p8 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 if written using the substituted arguments. [...]

Expression SFINAE: how to select template version based on whether type contains a function with one or more arguments

The immediate problem is that the argument you are passing to Test is not compatible with the YesType version.

For example, Detail::HasFindMethod<std::unordered_set<int>> will result in the following two Test signatures (because find would return an iterator):

        static YesType& Test(std::unordered_set<int>::iterator);

static NoType& Test(...);

You try to call Test with the argument 0, which is not convertible to iterator. Hence, the second one is picked.

As a solution, use a pointer:

        template <typename C> static YesType& 
Test(decltype(std::declval<C>().find(std::declval<const C::value_type&>()))*);
// ^

Then do the check with a nullptr argument:

        enum { value = sizeof(Test<T>(nullptr)) == sizeof(YesType) };

Now we would have ambiguity (the Test(...) would also match), so we can make that one a worse match:

        template <typename C, class ... Args> static NoType& Test(void*, Args...);

As shown in the other answers, this is still a comparatively convoluted solution (and there are more issues that prevent it from working in your instance, such as ambiguity between the overloads when the enable_if does work). Just explaining the particular stopper in your attempt here.

Expression SFINAE and hard errors in c++

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.

C++ expression SFINAE and ostream manipulators

First, fix your ostreamable class. Currently, your class requires T to be copy-constructible from 0. This is not the case for many classes. It should use std::declval to create the value instead:

template <class O>
struct is_ostreamable {
template <class T>
static auto check(int) -> decltype(std::declval<std::ostream &>() << std::declval<T>(), std::true_type());
template <class>
static auto check(...) -> std::false_type;

public:
static constexpr bool value{std::is_same_v<decltype(check<O>(0)), std::true_type>};
};

Two changes are made here:

  • The operand to decltype uses std::declval<T>() to create the object of type T. std::declval<T> is an (intentionally undefined) function template that generates an object of type T when used in an unevaluated operand (such as that to decltype, or sizeof, noexcept operator, etc.) without dependence on a specific construction signature (copy-construction from 0 in your case).

  • The parameter to check is replaced with int. The initializer of the value variable calls check with the argument 0, so this int parameter ensures that (int) ranks higher than (...) in overload resolution, so that the true_type overload gets chosen when possible.


You need to provide a special overload for function-style manipulators (std::endl, std::flush, etc.):

using manip = std::ostream& (*)(std::ostream&);

CustomOutput& operator<<(manip m) {
os << m;
return *this;
}

There is, unfortunately, no way to make the generic template version support this feature. This is because std::endl is a function template:

template <class CharT, class Traits>
std::basic_ostream<CharT, Traits>& endl(td::basic_ostream<CharT, Traits>& os);

For a function template to be used, the appropriate template arguments have to be determined. It is not possible to deduce the type-template parameter T as a generic template.

Anyway, this is probably the only special overload you are going to need.

Is this a valid way of performing Expression SFINAE in C++03?

Expression SFINAE is a bit gray. C++03 basically said nothing on the subject. It neither explicitly banned it nor explicitly allowed it. Contemporary implementations did not permit such constructs because it caused substantial implementation complexity and it's unclear whether it's meant to be allowed, and CWG was at one point leaning towards banning it (see the April, 2003 note) before it eventually reversed course, partially in light of decltype and constexpr that were added to C++11 (see the introduction to N2634).

This also all happened well before CWG started explicitly marking the DR status of issues whose resolutions are meant to apply retroactively.

I think the best advice here is simply "ask your compiler vendor". A compiler that supports expression SFINAE in its C++11 mode is unlikely to tear out that support in C++03 mode (the vendor may treat CWG 339 as a defect report and apply it retroactively, or consider it as an extension). OTOH, a compiler that never supported C++11 is unlikely to invest the substantial costs necessary for expression SFINAE to work (indeed, it did not work in a certain major compiler cough until relatively recently). I also suspect that a place still stuck with an 15-year-old language is unlikely to use the modern toolchains necessary for such support.

Expression SFINAE without enable_if?

You might prioritize your overloads with extra argument:

template <std::size_t N> struct OverloadPriority : OverloadPriority<N -1> {};
template <> struct OverloadPriority<0> {};

template <typename T>
auto serialize_impl(const T& t, OverloadPriority<1>) -> decltype(to_string(t))
{
return to_string(t);
}

template <typename T>
auto serialize_impl(const T& t, OverloadPriority<0>) -> std::string
{
return "<object>";
}

template <typename T>
decltype(auto) serialize(const T& t)
{
return serialize_impl(t, OverloadPriority<1>{});
}

Demo

out of class definition of function template using expression-SFINAE

The reason the compiler cannot match the definition with the declaration is the following:

[basic.lookup.unqual]/p7:

A name used in the definition of a class X outside of a complete-class context ([class.mem]) of X shall be declared in one of the following ways:

  • before its use in class X or be a member of a base class of X ([class.member.lookup]), or
  • [...]

where [class.mem]/p6:

A complete-class context of a class is a

  • function body ([dcl.fct.def.general]),
  • default argument,
  • noexcept-specifier, or
  • default member initializer

within the member-specification of the class.

That is, at the point of declaration:

template <typename T>
auto Serialize(const T &value) const -> decltype(SerializeNative(value), std::declval<void>());

the name SerializeNative is not found by the name lookup, because SerializeNative is declared after its use, while it is found in the definition, causing the mismatch.

In order to use SerializeNative in the expression SFINAE, you need to declare the private virtual functions before using their name in the return type of Serialize.

The error for SerializeNative(value) is not immediately reported, because that function could potentially be found in argument-dependent lookup, as soon as the type for value is known.

Understanding SFINAE

In C++98, SFINAE is done with either a return type or a function's dummy argument with default parameter

// SFINAE on return type for functions with fixed arguments (e.g. operator overloading)
template<class T>
typename std::enable_if< std::is_integral<T>::value, void>::type
my_function(T const&);

// SFINAE on dummy argument with default parameter for functions with no return type (e.g. constructors)
template<class T>
void my_function(T const&, std::enable_if< std::is_integral<T>::value, void>::type* = nullptr);

In both cases, substution of T in order to get the nested type type is the essence of SFINAE. In contrast to std::enable_if, your assert template does not have a nested type that can be used in substitution part of SFINAE.

See Jonathan Wakely's excellent ACCU 2013 presentation for more details and also for the C++11 expression SFINAE. Among others (as pointed out by @BartekBanachewicz in the comments) is is now also possible to use SFINAE in function template default arguments

// use C++11 default function arguments, no clutter in function's signature!
template<class T, class dummy = typename std::enable_if< std::is_integral<T>::value, void>::type>
void my_function(T const&);

SFINAE: What is happening here?

typename std::enable_if<Kern::dim == 2, bool>::type = true>

That says:

typename:

the following term defines a type

std::enable_if<Kern::dim == 2, bool>

This template defines a type of the second template parameter IF the condition in the first parameter is true. So here, if dimm == 2 is true, the template std::enable_if provide a type bool which can be accessed with the ::type.

If the condition was true, the term:

typename std::enable_if<Kern::dim == 3, bool>::type

becomes simply:

bool

Now you add = true after it. Did you use the bool value anywhere? NO! So it simply doesn't matter at all! you also can write:

typename std::enable_if<Kern::dim == 3, int>::type = 42

It will result in the same, as you did not use the value you define here!

The condition you check is in Kern::dim == 3. This one must be true or false.

If the condition is evaluated to false, the template enable_if did not contain a type and the expression fails. Here SFINAE comes into play. This failure will not be an error but makes the template definition "invisible" as it "can not" be used cause of the failure.

Add-On for the addition question in the comments:

Sure, you can add a name to your bool template default paramter and use it in your code below like this:

template<class Kern,
typename std::enable_if<Kern::dim == 2, bool>::type myVal = true>
inline void apply_kern(){
std::cout << "dim=2" << "\n";
std::cout << "bool val: " << myVal << std::endl;
}

BTW:
We often see SFINAE used in cases, where a simple template overload works the same way. Often the overload is easier to read ( here maybe not :-) ). I give it only as a hint: Check if SFINAE is really needed and think of a overload instead.

Template overload instead of SFINAE:

/* ----------------------------------------------
Define two kernels: characterized by their dimension
---------------------------------------------- */
struct Kern2 { static constexpr int dim = 2; };
struct Kern3 { static constexpr int dim = 3; };

/* ----------------------------------------------
Choose which function to evaluate based on
dimension of Kern (Kern::dim)
---------------------------------------------- */
template < int x > inline void apply_kern_impl();

template<>
inline void apply_kern_impl<2>() { std::cout << "dim=2" << "\n"; }

template<>
inline void apply_kern_impl<3>() { std::cout << "dim=3" << "\n"; }

template< typename T>
inline void apply_kern() { apply_kern_impl<T::dim>(); }

int main()
{
apply_kern<Kern2>(); // should print 'dim=2'
apply_kern<Kern3>(); // should print 'dim=3'

return 0;
}


Related Topics



Leave a reply



Submit