🎓 A1Learner
🔍
0 of 28 lessons completed

C++ SFINAE

SFINAE is the rule that a failed match inside a template is not an error by itself. The failed choice is set aside and the search for a matching choice goes on.

The same job written twice

The doubled job is written once for int and once for double. The two bodies do the same work and the duplication is the cost.

#include <iostream>

int doubled(int value) {
  return value * 2;
}

double doubled(double value) {
  return value * 2;
}

int main() {
  std::cout << doubled(7) << std::endl;
  std::cout << doubled(2.5) << std::endl;
  return 0;
}

One template switched by enable_if

The twiceOld template stays on only for whole number types. One definition serves the int call and the long call.

#include <iostream>
#include <type_traits>

template <typename T>
std::enable_if_t<std::is_integral_v<T>, T> twiceOld(T value) {
  return value * 2;
}

int main() {
  std::cout << twiceOld(7) << std::endl;
  std::cout << twiceOld(21L) << std::endl;
  return 0;
}

A switched off call is refused

The double call finds the template switched off and no other choice matches. The output shows the first refusal line and the full error runs much longer.

#include <iostream>
#include <type_traits>

template <typename T>
std::enable_if_t<std::is_integral_v<T>, T> twiceOld(T value) {
  return value * 2;
}

int main() {
  std::cout << twiceOld(2.5) << std::endl;
  return 0;
}

The same job with a concept

The requires clause states the whole number rule in one short line. The calls and the printed results match the enable_if version.

#include <concepts>
#include <iostream>

template <typename T>
requires std::integral<T>
T twiceNew(T value) {
  return value * 2;
}

int main() {
  std::cout << twiceNew(7) << std::endl;
  std::cout << twiceNew(21L) << std::endl;
  return 0;
}

Example

#include <concepts>
#include <iostream>
#include <type_traits>

template <typename T>
std::enable_if_t<std::is_integral_v<T>, T> twiceOld(T value) {
  return value * 2;
}

template <typename T>
requires std::integral<T>
T twiceNew(T value) {
  return value * 2;
}

int main() {
  std::cout << twiceOld(9) << std::endl;
  std::cout << twiceNew(9) << std::endl;
  return 0;
}

Key points

  • SFINAE stands for substitution failure is not an error.
  • Before concepts, a failed template match could fill the screen with error text.
  • std::enable_if_t switches a template off when its condition is false.
  • A switched off template is removed from the list of choices.
  • The removal is silent by design so other choices can still match.
  • A concept states the same rule in fewer words.
  • The concept form and the enable_if form accept the same calls in this lesson.

In this example, twiceOld gates its template with std::enable_if_t and twiceNew states the same rule with a requires clause. Both functions double the int value 9. The two printed lines match at 18 each.

Note

The old tool still appears in older code. New code can state the rule with a concept instead.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

Why is a switched off template removed without an error at that step?