Template Constraints with C++20 Concepts (and SFINAE Fallback)
C++20 concepts (`requires` clauses) replace decades of SFINAE incantations with readable predicate-style template constraints. The runnable accordions in this entry use the C++17 `std::enable_if_t` SFINAE form because the test compiler is GCC 9.2 (which lacks concepts), and the C++20 equivalent is shown in inline comments. Reach for concepts on any compiler that supports them; reach for SFINAE only when constrained by toolchain age.
870 views
16
#include <iostream>
#include <type_traits>
#include <vector>
#include <string>
#include <utility>
// C++20:
// template <typename T>
// concept Printable = requires(std::ostream& os, const T& t) { os << t; };
//
// template <Printable T> void show(const T& t) { std::cout << t << "\n"; }
//
// C++17 SFINAE equivalent: detect whether `os << t` is a valid expression via
// std::declval inside a void_t alias, then enable the overload only when it is.
template <typename, typename = void>
struct is_printable : std::false_type {};
template <typename T>
struct is_printable<T, std::void_t<decltype(std::declval<std::ostream&>() << std::declval<const T&>())>>
: std::true_type {};
template <typename T>
typename std::enable_if<is_printable<T>::value>::type
show(const T& t) {
std::cout << t << "\n";
}
struct NotPrintable { int x; };
int main() {
show(42);
show(std::string("hello"));
show(3.14);
// show(NotPrintable{1}); // would not compile: no matching overload.
std::cout << "is_printable<int>=" << is_printable<int>::value << "\n";
std::cout << "is_printable<NotPrintable>=" << is_printable<NotPrintable>::value << "\n";
return 0;
}The C++20 form uses concept Printable = requires(std::ostream& os, const T& t) { os << t; }; and reads exactly like a predicate: "T is Printable when os << t compiles". The C++17 SFINAE form here achieves the same compile-time test through three pieces: a primary trait that defaults to false_type, a partial specialisation that activates only when decltype(os << t) is well-formed (gated by std::void_t), and an enable_if on the function template that excludes the overload when the trait is false. The result is identical: show(NotPrintable{}) is a hard compile error pointing at "no matching overload" rather than a 200-line template-stack barf. Concepts are easier to write, easier to read, and produce vastly better error messages.
2 more snippets in this entry are available for premium members.
Upgrade to Premium