Home > other >  static_assert in templated member function of non-template class
static_assert in templated member function of non-template class

Time:01-10

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?

CodePudding user response:

[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;
    }
};

CodePudding user response:

Raymond Chen covered this issue in his blog:

How can I create a type-dependent expression that is always false?

auto lambda = [total](auto op, auto value) mutable
{
  ...
  static_assert(false, "Don't know what you are asking me to do.");
  ...
}; 

However, this does not compile because the static_assert fails immediately.

The reason is that the controlling expression for the static_assert is not dependent upon the type of the arguments, and therefore it is evaluated when the lambda is compiled, not when the lambda is invoked (and the implicit template instantiated).

In order to defer the static_assert to instantiation, we need to make it type-dependent.

Of what use is a type-dependent expression that is always false?

Last time, we saw how to create a type-dependent expression that is always false, and used it in a potentially-discarded statement so that the assertion failed only if the statement ended up being used.

Another case where you want to defer a static assertion failure to instantiation is if you want to reject a particular specialization.

So, you need to make the static_assert condition be dependent on the template argument. Raymond showed two ways to do that:

Using a custom helper template:

#include <type_traits>

template<typename>
inline constexpr bool always_false_v = false;

class TestClass
{
    ...

    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;
    }
};

Using sizeof():

#include <type_traits>

class TestClass
{
    ...

    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(!sizeof(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(!sizeof(Type*), "not implemented yet");
        return 0;
    }
};
  • Related