12

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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
LeDYoM
  • 949
  • 1
  • 6
  • 21
  • 2
    Possible duplicate: https://stackoverflow.com/questions/2127693/sfinae-sizeof-detect-if-expression-compiles (adding `static_assert` is trivial) – eerorika Mar 16 '19 at 20:25
  • @eerorika this is not quite a duplicate, since the question wants to be able to write the failing expression directly in static_assert, not in earlier declarations. I'm not sure it is possible, since templates can't be declared inside function bodies (other than generic lambdas), and so SFINAE can't be used. – Michael Veksler Mar 16 '19 at 21:17
  • 1
    The library fundamentals ts 2 specifies [`is_detected`](https://en.cppreference.com/w/cpp/experimental/is_detected). There's also an example implementation that you can use. – Timo Mar 16 '19 at 21:57
  • A bigger picture with more context might be in order. ([XY problem](https://en.wikipedia.org/wiki/XY_problem)?) Detecting something that does not compile suggests [SFINAE](https://en.cppreference.com/w/cpp/language/sfinae), so it may be useful to know why there should be an error in this case. – JaMiT Mar 17 '19 at 00:21
  • @JaMiT The context is that I have something similar to the example and I want to do a test to ensure nobody messes this expectation. – LeDYoM Mar 17 '19 at 08:59
  • @LeDYoM Sorry, I do not follow. (I thought it was obvious from me asking the question that your example does not explain the situation well enough for me.) I guess this is one of those questions I should just leave for someone else to deal with. – JaMiT Mar 17 '19 at 10:15

1 Answers1

3

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.

florestan
  • 4,405
  • 2
  • 14
  • 28