Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to express a static_assert for an expression that should not compile?

I would like to express a static_assert with the form:

static_assert(expression should not compile);

Let me add a complete example:

template <bool Big>
struct A{};

template <>
struct A<true>
{
    void a() {}
};

A<false> b;

static_assert(!compile(b.a()));
or
static_assert(!compile(A<false>::a()));

So, the idea is to be able to ensure that an expression (with valid syntax) will not compile.

It will be better if the solution only uses C++11 if possible.

like image 696
LeDYoM Avatar asked Mar 16 '19 20:03

LeDYoM


People also ask

What is the behavior if condition provided to static_ assert is evaluated as false?

If the condition is true, the static_assert declaration has no effect. If the condition is false, the assertion fails, the compiler displays the message in string_literal parameter and the compilation fails with an error.

Where is static_assert defined?

static_assert is a keyword defined in the <assert. h> header. It is available in the C11 version of C. static_assert is used to ensure that a condition is true when the code is compiled.

When to use static_ assert?

Use static_assert for assertions that should not occur on a usual basis.


1 Answers

OK, given the context of your question is somewhat vague, this answer might not be appropriate for your case. However, I found this a very interesting challenge.

Clearly, as stated in the comments, the solution will have to exploit some kind of (expression) SFINAE. Basically, what we need is a more generic variant of the detection idiom. Hovewer, there are mainly two problems here:

1) To make SFINAE kick in, we need some kind of templates.

2) To provide the compile(XXX) syntax, we need to create these templates "on the fly" inside a macro. Otherwise we would have to define a test function for each test in advance.

The second constraint makes things rather difficult. We can define structs and functions locally inside lambdas. Unfortunately, templates are not allowed there.

So here is, how far I was able to get (not 100% what you want, but relatively close).

Usually, the expression-SFINAE-detectors leverage either (template) function overloading or class template specialisation. As both are not allowed inside a lambda, we need an additional layer: a functor that takes a bunch of lambdas and calls the one that fits the call arguments best. This is often used in combination with std::variant.

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; }; 
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>; 

Now we can create a detector like this:

auto detector =    overloaded{
     [](auto, auto) -> std::false_type {return {};}
    ,
    [](auto x, int)-> decltype(decltype(x)::a(), std::true_type{}){ return {};}
    };

static_assert(!detector(A<false>{}, int{}));
static_assert(detector(A<true>{}, int{}));

Now, we can define a macro, that defines and calls a dector for the desired expression:

#define compile(obj, xpr)                                                   \
  []() {                                                                    \
    auto check =                                                            \
        overloaded{[](auto&&, auto) -> std::false_type { return {}; },      \
                   [](auto&& x, int) -> decltype(x xpr, std::true_type{}) { \
                     return {};                                             \
                   }};                                                      \
    return decltype(check(obj, int{})){};                                   \
  }()

This macro creates a lambda, substitutes the xpr into the detector, and performs type deduction on decltype(x) to make SFINAE kick in. It can be used as follows:

static_assert(!compile(b, .a()));
static_assert(compile(a, .a()));

int x = 0;
static_assert(compile(x, *= 5));
static_assert(!compile(x, *= "blah"));

Unfortunately, it won't work with a typename as first argument. Therefore we need a second macro for those kinds af tests:

#define compile_static(clazz, xpr)                                       \
  []() {                                                                 \
    auto check = overloaded{                                             \
        [](auto, auto) -> std::false_type { return {}; },                \
        [](auto x, int) -> decltype(decltype(x) xpr, std::true_type{}) { \
          return {};                                                     \
        }};                                                              \
    return decltype(check(std::declval<clazz>(), int{})){};              \
  }()

static_assert(!compile_static(A<false>, ::a()));
static_assert(compile_static(A<true>, ::a()));

As stated above, this is not 100% what you requested, as we'll always need an additional , to separate the macro arguments. Also, it needs two separate macros. Maybe this is something that can be impoved using the preprocessor to detect whether the xpr argument starts with ::. And, of course, there might be cases, where it doesn't work. but perhaps it is a starting point.

It requires c++17, but seems to work with gcc >= 7, clang >= 5 and even msvc 19.

Here is a full example.

like image 128
florestan Avatar answered Sep 29 '22 18:09

florestan