Explain C++ Sfinae to a Non-C++ Programmer

Explain C++ SFINAE to a non-C++ programmer

Warning: this is a really long explanation, but hopefully it really explains not only what SFINAE does, but gives some idea of when and why you might use it.

Okay, to explain this we probably need to back up and explain templates a bit. As we all know, Python uses what's commonly referred to as duck typing -- for example, when you invoke a function, you can pass an object X to that function as long as X provides all the operations used by the function.

In C++, a normal (non-template) function requires that you specify the type of a parameter. If you defined a function like:

int plus1(int x) { return x + 1; }

You can only apply that function to an int. The fact that it uses x in a way that could just as well apply to other types like long or float makes no difference -- it only applies to an int anyway.

To get something closer to Python's duck typing, you can create a template instead:

template <class T>
T plus1(T x) { return x + 1; }

Now our plus1 is a lot more like it would be in Python -- in particular, we can invoke it equally well to an object x of any type for which x + 1 is defined.

Now, consider, for example, that we want to write some objects out to a stream. Unfortunately, some of those objects get written to a stream using stream << object, but others use object.write(stream); instead. We want to be able to handle either one without the user having to specify which. Now, template specialization allows us to write the specialized template, so if it was one type that used the object.write(stream) syntax, we could do something like:

template <class T>
std::ostream &write_object(T object, std::ostream &os) {
return os << object;
}

template <>
std::ostream &write_object(special_object object, std::ostream &os) {
return object.write(os);
}

That's fine for one type, and if we wanted to badly enough we could add more specializations for all the types that don't support stream << object -- but as soon as (for example) the user adds a new type that doesn't support stream << object, things break again.

What we want is a way to use the first specialization for any object that supports stream << object;, but the second for anything else (though we might sometime want to add a third for objects that use x.print(stream); instead).

We can use SFINAE to make that determination. To do that, we typically rely on a couple of other oddball details of C++. One is to use the sizeof operator. sizeof determines the size of a type or an expression, but it does so entirely at compile time by looking at the types involved, without evaluating the expression itself. For example, if I have something like:

int func() { return -1; }

I can use sizeof(func()). In this case, func() returns an int, so sizeof(func()) is equivalent to sizeof(int).

The second interesting item that's frequently used is the fact that the size of an array must to be positive, not zero.

Now, putting those together, we can do something like this:

// stolen, more or less intact from: 
// http://stackoverflow.com/questions/2127693/sfinae-sizeof-detect-if-expression-compiles
template<class T> T& ref();
template<class T> T val();

template<class T>
struct has_inserter
{
template<class U>
static char test(char(*)[sizeof(ref<std::ostream>() << val<U>())]);

template<class U>
static long test(...);

enum { value = 1 == sizeof test<T>(0) };
typedef boost::integral_constant<bool, value> type;
};

Here we have two overloads of test. The second of these takes a variable argument list (the ...) which means it can match any type -- but it's also the last choice the compiler will make in selecting an overload, so it'll only match if the first one does not. The other overload of test is a bit more interesting: it defines a function that takes one parameter: an array of pointers to functions that return char, where the size of the array is (in essence) sizeof(stream << object). If stream << object isn't a valid expression, the sizeof will yield 0, which means we've created an array of size zero, which isn't allowed. This is where the SFINAE itself comes into the picture. Attempting to substitute the type that doesn't support operator<< for U would fail, because it would produce a zero-sized array. But, that's not an error -- it just means that function is eliminated from the overload set. Therefore, the other function is the only one that can be used in such a case.

That then gets used in the enum expression below -- it looks at the return value from the selected overload of test and checks whether it's equal to 1 (if it is, it means the function returning char was selected, but otherwise, the function returning long was selected).

The result is that has_inserter<type>::value will be l if we could use some_ostream << object; would compile, and 0 if it wouldn't. We can then use that value to control template specialization to pick the right way to write out the value for a particular type.

C++ SFINAE examples?

Heres one example (from here):

template<typename T>
class IsClassT {
private:
typedef char One;
typedef struct { char a[2]; } Two;
template<typename C> static One test(int C::*);
// Will be chosen if T is anything except a class.
template<typename C> static Two test(...);
public:
enum { Yes = sizeof(IsClassT<T>::test<T>(0)) == 1 };
enum { No = !Yes };
};

When IsClassT<int>::Yes is evaluated, 0 cannot be converted to int int::* because int is not a class, so it can't have a member pointer. If SFINAE didn't exist, then you would get a compiler error, something like '0 cannot be converted to member pointer for non-class type int'. Instead, it just uses the ... form which returns Two, and thus evaluates to false, int is not a class type.

What does it mean when one says something is SFINAE-friendly?

When it allows substitution failure without hard error (as static_assert).

for example

template <typename T>
void call_f(const T& t)
{
t.f();
}

The function is declared for all T, even those which don't have f, so you cannot do SFINAE on call_f<WithoutF> as the method does exist. (Demo of non compiling code).

With following change:

template <typename T>
auto call_f(const T& t) ->decltype(t.f(), void())
{
t.f();
}

The method exists only for valid T.
so you can use SFINAE as

template<typename T>
auto call_f_if_available_impl(const T& t, int) -> decltype(call_f(t))
{
call_f(t);
}

template<typename T>
auto call_f_if_available_impl(const T& t, ...)
{
// Do nothing;
}

template<typename T>
auto call_f_if_available(const T& t)
{
call_f_if_available_impl(t, 0);
}

Note the int = 0 and ... is to order the overload.
Demo

--

An other case is when the template add special parameter to apply SFINAE for specialization:

template <typename T, typename Enabler = void> struct S;

And then

// Specialization only available for T which respect the traits.
template <typename T>
struct S<T, std::enable_if_t<my_type_trait<T>::value>>
{
};

c++ call c function if it does exist

You might use overload with lower priority to solve your issue:

// "C-Header"
struct SomeStruct
{
int indicatorMember;
};

// Present or not
extern "C" void someFun(struct SomeStruct* somePointer){

}

// Fallback
void someFun(...) { /*Empty*/ }

void someCPPcode()
{
SomeStruct s;
// do something

someFun(&s);

// do something other
}

it would also be possible to decide if this function exists, if a certain member is part of a structure. so, if indicatorMember does exist, the function also exists.

There are several ways to detect presence of member, such as use of std::experimental::is_detected.

but outside template, you still have issue:

decltype(auto) someFunIfExists([[maybe_unused]] SomeStruct* p)
{
if constexpr (has_someFunc<SomeStruct>::value) {
return someFun(p); // Not discarded as you might expect -> Error
}
}

as if constexpr (false) { static_assert(false); } is invalid).

So you have to wrap the function inside template:

template <typename T>
decltype(auto) someFunIfExists([[maybe_unused]] T* p)
{
if constexpr (has_someFunc<T>::value) {
return someFun(p);
}
}

void someCPPcode(){
SomeStruct s;
// do something

someFunIfExists(&s);

// do something other
}

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

How to simplify complicated SFINAE syntax, in pre-C++11, C++11, 14 and 17?

If you are working with C++11 (the example code contains std::enable_if, so I guess this is the case) or a successive revision, I would use a static_assert in this case:

int fun() {
static_assert(sizeof(int)==4, "!");
return 0;
}

int main() {
fun();
}

You don't have a set of functions from which to pick a working one up.

As I've been said once, this is more a substitution failure is always an error than a substitution failure is not an error.

What you want is a compile-time trigger and a static_assert does it with gentle error messages.

Of course, it's also far easier to read than a complicated sfinae expression too!!


If you want to choose between two functions and you don't want to use template machinery or macros, do not forget that overloading is part of the language (pre-C++11 working example):

#include <iostream>

template<bool> struct tag {};
int fun(tag<true>) { return 0; }
int fun(tag<false>) { return 1; }
int fun() { return fun(tag<sizeof(int) == 4>()); }

int main() {
std::cout << fun() << std::endl;
}

This can be easily extended to the cases where the functions are more than two:

#include <iostream>

template<int> struct tag {};
int fun(tag<0>) { return 0; }
int fun(tag<1>) { return 1; }
int fun(tag<2>) { return 2; }

int fun(bool b) {
if(b) { return fun(tag<0>()); }
else { return fun(tag<(sizeof(int) == 4) ? 1 : 2>());
}

int main() {
std::cout << fun(false) << std::endl;
}

You can put those functions in an anonymous namespace and get away with them.


Of course, note also that in pre-C++11 we were authorized to write enable_if and all the other things from type_traits for ourselves.

As an example:

template<bool b, typename = void>
struct enable_if { };

template<typename T>
struct enable_if<true, T> { typedef T type; };

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.

How does changing a template argument from a type to a non-type make SFINAE work?

Mainly because [temp.over.link]/6 does not talk about template default argument:

Two template-heads are equivalent if their template-parameter-lists have the same length, corresponding template-parameters are equivalent, and if either has a requires-clause, they both have requires-clauses and the corresponding constraint-expressions are equivalent. Two template-parameters are equivalent under the following conditions:

  • they declare template parameters of the same kind,

  • if either declares a template parameter pack, they both do,

  • if they declare non-type template parameters, they have equivalent types,

  • if they declare template template parameters, their template parameters are equivalent, and

  • if either is declared with a qualified-concept-name, they both are, and the qualified-concept-names are equivalent.

Then by [temp.over.link]/7:

Two function templates are equivalent if they are declared in the same scope, have the same name, have equivalent template-heads, and have return types, parameter lists, and trailing requires-clauses (if any) that are equivalent using the rules described above to compare expressions involving template parameters.

... the two templates in your first example are equivalent, while the two templates in your second example are not. So the two templates in your first example declare the same entity and result in an ill-formed construct by [class.mem]/5:

A member shall not be declared twice in the member-specification, ...

How to force SFINAE to choose the second definition of structure?

as @Brian said, you should put the requirements at the primary template if the requirements are for all specializations, and put other requirements for each specialization at their own declarations:

template<typename T, typename = std::void_t</* global requirements */>>
struct S;
template<typename T>
struct S<T, std::void_t</* requirements for this specialization */>>;

and if you want one of specialization is prior to others, you can add its negative requirements to other specializations:

template<typename T, typename = std::void_t</* global requirements */>>
struct S;
template<typename T>
struct S<T, std::void_t<std::enable_if_t</* conditions for this specialization */>>>;
template<typename T>
struct S<T, std::void_t<std::enable_if_t<!/* conditions for the former specialization */>, /* requirements for this specialization */>>;

for your example, it should be like this:

template<typename Lambda>
struct is_valid_construction{
template<typename T, typename = void>
struct helper : std::false_type{};
template<typename T>
struct helper<T, std::void_t<decltype(std::declval<Lambda>()(std::declval<T>()))>> : std::true_type{};

template<typename V, typename = void>
struct evaluate;
template<typename V>
struct evaluate<V, std::enable_if_t<helper<V>::value>>;
template<typename V>
struct evaluate<V, std::void_t<std::enable_if_t<!helper<V>::value>, decltype(std::declval<Lambda>()(std::declval<int>()))>>;
};

by the way, you can use std::is_invocable to simplify this code:

template<typename Lambda>
struct is_valid_construction{
template<typename V, typename = void>
struct evaluate;
template<typename V>
struct evaluate<V, std::enable_if_t<std::is_invocable_v<Lambda, V>>>;
template<typename V>
struct evaluate<V, std::enable_if_t<!std::is_invocable_v<Lambda, V> && std::is_invocable_v<Lambda, int>>>;
};

SFINAE with boost enable if

Don't forget, converting assignment can be implemented by template functions, but copy and move assignment operators can't.

A user-declared copy assignment operator X::operator= is a non-static non-template member function of class X with exactly one parameter of type X, X&, const X&, volatile X& or const volatile X&.

If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy assignment operator is defined as deleted; otherwise, it is defined as defaulted

A user-declared move assignment operator X::operator= is a non-static non-template member function of class X with exactly one parameter of type X&&, const X&&, volatile X&&, or const volatile X&&.

and the note

Because a template assignment operator or an assignment operator taking an rvalue reference parameter is never a copy assignment operator, the presence of such an assignment operator does not suppress the implicit declaration of a copy assignment operator. Such assignment operators participate in overload resolution with other assignment operators, including copy assignment operators, and, if selected, will be used to assign an object.

(above quotes found in section 12.8, wording from draft n3936)



Related Topics



Leave a reply



Submit