This page introduces the Constraints
pattern, which can reduce the complexity of SFINAE implementation.
In C++17, in order to switch function declaration according to input types, typical C++ programmers write codes like the following.1
template <typename T,
std::enable_if_t</* SFINAE condition */, std::nullptr_t> = nullptr>
void func(T&& t) {
/* ... */
}
Those who don’t familiar with SFINAE might feel the above code is hard to read. It may be because the code uses std::nullptr_t
and nullptr
, which don’t seem to relate to the type T
. In addition, more SFINAE conditions make the length of template parameters of std::enable_if_t
, which leads to a reduction in readability.
In such a situation, the meta function Constraints
could improve readability.
namespace detail {
template <typename... Args>
struct ConstraintsImpl {
using Type = std::nullptr_t;
};
} // namespace detail
template <typename... Args>
using Constraints = typename detail::ConstraintsImpl<Args...>::Type;
// usage
template <typename T,
Constraints<std::enable_if_t</* SFINAE condition 1 */>,
std::enable_if_t</* SFINAE condition 2 */>,
/* SFINAE conditions... */> = nullptr>
void func(T&& t) {
/* ... */
}
Constraints
just always returns std::nullptr_t
. This idiom hides std::nullptr_t
, which could improve readability.
// example
template <typename T,
Constraints<std::enable_if_t<std::is_default_constructible_v<T>>,
std::enable_if_t<std::is_nothrow_assignable_v<T>>> = nullptr>
void func(T&& t) noexcept {
std::cout << "T is default constructible and nothrow assignable" << std::endl;
}
It may be true that the appearance is not very different from the original code. However, I found that it makes SFINAE codes more readable than I expected, so I am willing to use this idiom instead of enabler
or ALWAYS_TRUE
idiom.
See Entry is not found - Secret Garden(Instrumental)(Japanese) and std::enable_ifを使ってオーバーロードする時、enablerを使う? - Qiita(Japanese) to understand why C++ programmers often use
std::enable_if
andstd::nullptr_t
↩︎