c++c++17sfinaeif-constexpr

How can `if constexpr` be made SFINAE-friendly?


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?


Solution

  • C++20 and newer:

    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;?).

    C++17:

    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>)