c++c++17type-traitsstatic-assertcompile-time-type-checking

c++ static_assert fails on both branches of an 'if constexpr statement'


I'm trying to determine at compile time if a specific type is of type std::pair. When I compile the code below, I get the assertion fail on both branches (that is, both "HERE1", and "HERE2"). If I remove the static_asserts and uncomment the prints, I get what I expect: That is "HERE1" for is_pair_type<T::value_type>, and "HERE2" for is_pair_type< T >.

I guess this means the compiler can't evaluate the expression at compile time, but I don't understand why.

Using: MS VS2019, with MSVC version 14.29.30037

Thanks.


template< class T >             struct is_pair : std::false_type {};
template< class T1, class T2 >  struct is_pair< std::pair< T1, T2 > > : std::true_type {};
template< class T >             struct is_pair_d : is_pair<typename std::decay<T>::type> {};
// a helper function for value
template<class T> struct is_pair_type {
    static constexpr bool const value = is_pair_d<T>::value;
};

int main()
{
    using T = std::map<int, float>;
    T blabla;

    if constexpr (is_pair_type<T>::value)
    {
        //std::cout << "HERE1" << "\n";
        static_assert(false, "HERE1");
    }
    else
    {
        //std::cout << "HERE2" << "\n";
        static_assert(false, "HERE2");
    }
    ...

Solution

  • Constexpr if is supposed to work with template:

    Outside a template, a discarded statement is fully checked. if constexpr is not a substitute for the #if preprocessing directive:

    void f() {
        if constexpr(false) {
            int i = 0;
            int *p = i; // Error even though in discarded statement
        }
    }
    

    And

    Note: the discarded statement can't be ill-formed for every possible specialization:

    template <typename T>
    void f() {
         if constexpr (std::is_arithmetic_v<T>)
             // ...
         else
           static_assert(false, "Must be arithmetic"); // ill-formed: invalid for every T
    }
    

    You can wrap the code into a function template like:

    template<class T> struct dependent_false : std::false_type {};
    template <typename T>
    void foo() {
        if constexpr (is_pair_type<T>::value)
        {
            std::cout << "HERE1" << "\n";
            static_assert(dependent_false<T>::value, "HERE1");
        }
        else
        {
            std::cout << "HERE2" << "\n";
            static_assert(dependent_false<T>::value, "HERE2");
        }
    }
    

    Then

    using T = std::map<int, float>;
    foo<T>(); // static_assert fails only on the else part
    

    LIVE