Walter
Walter

Reputation: 45414

When to use `static_assert` instead of SFINAE?

I have been using (and seen used) static_assert to flag undesired values of template parameter values. However, for all cases I came across it seems better and more elegant to disable those undesired values via SFINAE.

For example:

template<typename T,
         class = std::enable_if<std::is_floating_point<T>::value>::type>
struct Foo { ... };

instead of:

template<typename T>
struct Foo
{
    static_assert(std::is_floating_point<T>::value,
                  "Foo<T>: T must be floating point :-(");
    ...
};

So my question: when should I use static_assert instead of SFINAE and why?

EDIT:

I think what I've learned so far is the following:

1. SFINAE is a versatile and powerful but potentially very complicated tool that can be used for many tasks, including function overload resolution (which some seem to regard as its only purpose).

2. SFINAE can be used in a relatively simple way wherever static_assert can, except that it appears in the declaration (of a class or function) rather than its definition (or is is possible to insert a static_assert into, say, a class forward declaration?). That makes more verbatim and hence clearer code. However, because SFINAE is complicated, it tends to be harder to get right than a simple static_assert.

3. On the other hand static_assert has the benefit of a clearer compiler error message, which some seem to regard as the main purpose of both.

Upvotes: 31

Views: 5569

Answers (4)

Xeo
Xeo

Reputation: 131789

For one, using SFINAE may lead to another overload being picked that was originally a worse match and wouldn't be considered.

And in the situation that there are other overloads, but non of them is viable, you get some nice things like this:

#include <type_traits>

void f(int){}
void f(bool){}
void f(char){}
void f(float){}
void f(long){}
void f(double){}
void f(short){}
void f(unsigned){}
void f(void*){}
void f(void (*)()){}

template<class C, class T = int>
using EnableIf = typename std::enable_if<C::value, T>::type;

template<class T>
struct sfinae_false : std::false_type{};

template<class T> 
void f(T&&, EnableIf<sfinae_false<T>> = 0){}

int main(){ struct X{}; f(X()); }

Output:

source.cpp: In function 'int main()':
source.cpp:23:30: error: no matching function for call to 'f(main()::X)'
source.cpp:23:30: note: candidates are:
source.cpp:3:6: note: void f(int)
source.cpp:3:6: note:   no known conversion for argument 1 from 'main()::X' to 'int'
source.cpp:4:6: note: void f(bool)
source.cpp:4:6: note:   no known conversion for argument 1 from 'main()::X' to 'bool'
source.cpp:5:6: note: void f(char)
source.cpp:5:6: note:   no known conversion for argument 1 from 'main()::X' to 'char'
source.cpp:6:6: note: void f(float)
source.cpp:6:6: note:   no known conversion for argument 1 from 'main()::X' to 'float'
source.cpp:7:6: note: void f(long int)
source.cpp:7:6: note:   no known conversion for argument 1 from 'main()::X' to 'long int'
source.cpp:8:6: note: void f(double)
source.cpp:8:6: note:   no known conversion for argument 1 from 'main()::X' to 'double'
source.cpp:9:6: note: void f(short int)
source.cpp:9:6: note:   no known conversion for argument 1 from 'main()::X' to 'short int'
source.cpp:10:6: note: void f(unsigned int)
source.cpp:10:6: note:   no known conversion for argument 1 from 'main()::X' to 'unsigned int'
source.cpp:11:6: note: void f(void*)
source.cpp:11:6: note:   no known conversion for argument 1 from 'main()::X' to 'void*'
source.cpp:12:6: note: void f(void (*)())
source.cpp:12:6: note:   no known conversion for argument 1 from 'main()::X' to 'void (*)()'
source.cpp:21:6: note: template<class T> void f(T&&, EnableIf<sfinae_false<T> >)
source.cpp:21:6: note:   template argument deduction/substitution failed:

Upvotes: 6

StackedCrooked
StackedCrooked

Reputation: 35485

I think static_assert is the right choice if you want to enforce that T is a floating point type. This method states your intent more clearly than the SFINAE solution.

Upvotes: 9

log0
log0

Reputation: 10917

static_assert makes the compilation fail. SFINAE allows you to remove one possible overload.

Upvotes: 15

Tadeusz Kopec for Ukraine
Tadeusz Kopec for Ukraine

Reputation: 12403

You use SFINAE, if you want another overload to be used, and static_assert if none of them would fit such parameter.

Upvotes: 20

Related Questions