29

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.

Olivia Stork
  • 4,660
  • 5
  • 27
  • 40
Walter
  • 44,150
  • 20
  • 113
  • 196
  • 3
    Can you explain why you think it's better with SFINAE? – R. Martinho Fernandes Aug 16 '12 at 10:02
  • Maybe our answers are oriented by the vocabulary. In you question I think you should replace SFINAE by `std::enable_if`. It sounds strange to want to generate an error with a mechanism that emphasis on the fact that it is not an error. SFINAE = Substitution Failure Is Not An Error – log0 Aug 17 '12 at 12:18

4 Answers4

19

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

Tadeusz Kopec for Ukraine
  • 12,283
  • 6
  • 56
  • 83
14

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

log0
  • 10,489
  • 4
  • 28
  • 62
  • In the case shown, both make compilation to fail if you write `Foo`. – R. Martinho Fernandes Aug 16 '12 at 10:23
  • 1
    SFINAE disables a certain template parameter. If the compiler cannot find another match, compilition will fail. – Walter Aug 16 '12 at 12:10
  • @R.MartinhoFernandes I just read your blog/page on *Remastered enable_if* and was missing the definition of the template `all<>` used to combine several condition types. Where can I find it? – Walter Aug 16 '12 at 13:05
  • @Walter it's on a previous post: http://rmartinho.github.com/2012/05/29/type-traits-galore.html#conditions_and_logical_metaops – R. Martinho Fernandes Aug 16 '12 at 13:32
9

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.

StackedCrooked
  • 34,653
  • 44
  • 154
  • 278
  • I disagree. consider the forward declaration `template class some_class;`. How can I know that that class only accepts integral type template parameters if the `static_assert` comes only in the class definition? – Walter Aug 16 '12 at 12:09
  • @Walter SFINAE is not applicable in this instance, so how does it matter? In any case with a bit of gymnastics you can write something like `template>...> class some_class;` (where `Requires` is a trick like those used in Boost.Concepts), although I cannot vouch for the legitimacy (and portability) of such a thing. – Luc Danton Aug 17 '12 at 05:11
6

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:
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • 3
    A nasty property of SFINAE is that you need to make sure that multiple overloads have non-overlapping conditions, otherwise you still get an error. I greatly prefer tag dispatching for selective overloading. – TemplateRex Aug 16 '12 at 10:20
  • 1
    Given that the example in the question is a class template rather than a function template, does this answer amount to "you are correct for classes, but haven't considered functions"? Or are there advantages to static asserts for classes too? – Steve Jessop Aug 16 '12 at 10:31
  • @SteveJessop: Well, SFINAE doesn't really apply to class templates, since it *is* a hard error there ("no member named 'type' in 'std::enable_if' ...). – Xeo Aug 16 '12 at 10:39
  • **1** I disagree that SFINAE doesn't apply to classes. **2** I agree that for functions SFINAE may result in undesired surprises. **3** I think that SFINAE is much easier to mess up and get wrong (as in this answer) than `static_assert` – Walter Aug 16 '12 at 12:00
  • using SFINAE on the type of a function argument is always dangerous. Better to use it on the return type. – Walter Aug 16 '12 at 12:03
  • @Walter: It's not "dangerous", it's just something I used here because I felt like it. Normally, I employ [new-style SFINAE](http://rmartinho.github.com/2012/06/01/almost-static-if.html) or [expression SFINAE](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2634.html). – Xeo Aug 16 '12 at 12:15
  • @Walter SFINAE only applies to so-called template argument deduction, which is only available to function templates, not class templates. This doesn't mean that your example cannot be used in tandem with SFINAE (I suspect that `template void foo(T, Foo = {});` is such a situation), but it is far from idiomatic: code involved in SFINAE is usually left to function templates, and not pushed to class templates. Use of `std::enable_if` in code is neither necessary nor sufficient for SFINAE. – Luc Danton Aug 16 '12 at 12:43
  • 3
    @Luc "which is only available to function templates". That's not actually true. SFINAE is active during argument deduction for class template partial specializations too. – Johannes Schaub - litb Aug 16 '12 at 19:04
  • @LucDanton **NO** You can disable a class template with SFINAE, for example by having an additional template parameter defaulting to `std::enable_if::type`. Such a class can be invoked for a first parameter not matching the condition only by explicitily providing a second parameter, i.e. by deliberate deceit. Such cases can then still be caught with a `static_assert`. – Walter Sep 06 '14 at 13:00
  • @Walter My point still stands. Such ‘disabling’ will only occur during overload resolution for a function template or, as Johannes rightfully pointed out, class template specialization selection. While this technique is available, I’ve never seen a use for it—it should be clearer to put the constraints on the function templates/class template specialization directly. Concepts-lite, which adapts present practice into actual language features, has no equivalent either (e.g. no `requires` clause allowed on a primary template declaration). – Luc Danton Sep 07 '14 at 06:14