Search code examples
c++visual-c++

static_assert in templated member function of non-template class


I finally switched to MSVC 2022 in the last couple days and am getting a static_assert from code that had previously been working fine.

I have a type that needs to have a member implemented differently based on whether a template parameter type is trivally constructable and destructable or not, but have not yet actually implemented any of that logic. I've been using static_assert(false, "not yet implemented") as a guard against accidental use of the member.

I've pared it down to the following example:

#include <type_traits>

class TestClass
{
    size_t MemberFn() { /* shared stuff between trivial and non-trivial */
        return 0;
    }
    template<typename Type>
    size_t MemberFn(std::enable_if_t<!std::is_trivially_constructible_v<Type> || !std::is_trivially_destructible_v<Type>>* = nullptr)
    {
        static_assert(false, "not implemented yet");
        return 0;
    }
    template<typename Type>
    size_t MemberFn(std::enable_if_t<std::is_trivially_constructible_v<Type> && std::is_trivially_destructible_v<Type>>* = nullptr)
    {
        static_assert(false, "not implemented yet");
        return 0;
    }
};

When I try building this I get the following (and similar for the second member template):

2>D:\projects\TestLib\TestLib\testlib.h(18,17): error C2338: static_assert failed: 'not implemented yet'
2>D:\projects\TestLib\TestLib\testlib.h(16,9): message : This diagnostic occurred in the compiler generated function 'size_t TestClass::MemberFn(enable_if<!std::is_trivially_constructible_v<Type,>||!std::is_trivially_destructible_v<Type>,void>::type *)'

Note that I do not actually have a call to this function anywhere, and the diagnostic does not tell me what actual type the compiler is trying to use. Basically I wish to go back to this particular function being ignored as it did with MSVC 2019.

I am compiling with /std:c++latest and /permissive- and would prefer to keep those.

What am I missing here?


Solution

  • This is because your compiler has not implemented a solution for DR2518 which states that

    • If the value of the expression when so converted is true or the expression is evaluated in the context of a template definition, the declaration has no effect. Otherwise, the static_assert-declaration fails

    Since you have static_assert(false, "..."); in a template definition that is uninstantiated, it should not be refused by the compiler.

    Implementations which haven't implemented a solution for the above defect report still abide by the old rules, shown in the old part of this answer:


    [dcl.pre]/10

    In a static_assert-declaration, the constant-expression is contextually converted to bool and the converted expression shall be a constant expression ([expr.const]). If the value of the expression when so converted is true, the declaration has no effect. Otherwise, the program is ill-formed, and the resulting diagnostic message ([intro.compliance]) should include the text of the string-literal, if one is supplied.

    By putting false in your static_assert you've made the program ill-formed and the compiler is correct when rejecting the program.


    A possible workaround could be to make the assertion dependent on the template parameter, at least superficially.

    template<class>
    struct always_false : std::false_type {};
    
    template<class T>
    inline constexpr bool always_false_v = always_false<T>::value;
    
    class TestClass {
        size_t MemberFn() { /* shared stuff between trivial and non-trivial */
            return 0;
        }
        template <typename Type>
        size_t MemberFn(
            std::enable_if_t<!std::is_trivially_constructible_v<Type> ||
                             !std::is_trivially_destructible_v<Type>>* = nullptr) {
            static_assert(always_false_v<Type>, "not implemented yet");
            return 0;
        }
        template <typename Type>
        size_t MemberFn(
            std::enable_if_t<std::is_trivially_constructible_v<Type> &&
                             std::is_trivially_destructible_v<Type>>* = nullptr) {
            static_assert(always_false_v<Type>, "not implemented yet");
            return 0;
        }
    };