I have a code using "classic" SFINAE.
template<class M> auto power(M const& elem) -> decltype(std::norm(elem)) { return std::norm(elem); }
template<class M, class = std::enable_if_t<(M::rank::value >= 1)>>
auto power(M const& array) {
return accumulate(begin(array), end(array), 0.0, [](auto const& alpha, auto const& omega) { return alpha + power(omega); });
}
This is a recursive (across dimensions or ranks) "power" function that ultimately gives the sum of the squares of the elements by calculating the power of subelements of lower dimension.
Modern C++ incentivizes the use if constexpr
to avoid using SFINAE, like below.
template<class M>
auto power(M const& array) {
if constexpr(M::rank::value >= 1) {
return accumulate(begin(array), end(array), 0.0, [](auto const& alpha, auto const& omega) { return alpha + power(omega); });
} else {
return std::norm(array);
}
}
The problem is that it seems that the expression in the if constexpr
has to be a valid expression in the first place.
Elements don't have a member type called rank
, so there is a compilation error.
Is there a trick to make the if constexpr
predicate if the expression is invalid?
if constexpr (requires{requires std::bool_constant</*condition*/>::value;})
There's also a less verbose option:
if constexpr (requires{requires /*condition*/;})
But the latter is worse, because it causes a hard error if the condition is not known at compile-time, while the former just returns false.
The former is used in the standard library (see also In a nested requirement, why use requires bool_constant<X>::value;
instead of requires X;
?).
Use the detection idiom:
#include <type_traits>
namespace detail
{
template <typename ...P> struct void_type {using type = void;};
template <typename DummyVoid, template <typename...> typename A, typename ...B> struct is_detected : std::false_type {};
template <template <typename...> typename A, typename ...B> struct is_detected<typename void_type<A<B...>>::type, A, B...> : std::true_type {};
}
template <template <typename...> typename A, typename ...B>
inline constexpr bool is_detected = detail::is_detected<void, A, B...>::value;
Then:
template <typename M>
using CheckRank = std::enable_if_t<M::rank::value >= 1>;
if constexpr (is_detected<CheckRank, M>)