11

I want to implement my_static_assert that slightly differs from c++17 single-parametric static_assert: if condition inside my_static_assert is not known at compile time, it should pass.

The second my_static_assert in the following example should pass but if I would use static_assert it will fail.

#include <iostream>

int x, y;
constexpr int f1() { return 0; }
constexpr int f2() { return 0; }
int f3() { return x; }
int f4() { return y; }
constexpr int sum(int a, int b) { return a + b; }

int main() {
    std::cin >> x >> y;

    // it should fail 
    my_static_assert(sum(sum(f1(), f2()), sum(f1(), f1())) != 0);

    // it should pass
    my_static_assert(sum(sum(f1(), f2()), sum(f4(), sum(f3(), f1()))) != 0);
}

If you want to know why this question arised:

I am building expressions using leaf functions f1,f2,f3,f4 and operations on the expression nodes: sum,mul,div,sub. Leafs that are known at compile time contain value that is always 0.

I am trying to check that my expressions contain at least one element that is not known at compile time.

  • I don't think this is possible :( – L. F. May 01 '19 at 13:16
  • Looks like there are a couple options in [here](https://stackoverflow.com/questions/8936549/constexpr-overloading). Not really sure if this should be a dupe though. – NathanOliver May 01 '19 at 13:17
  • This is similar to `is_constexpr`. There is [this question](https://stackoverflow.com/questions/13299394/is-is-constexpr-possible-in-c11) but it concluded there is no standards-compliant way to do it – Artyer May 01 '19 at 17:36

1 Answers1

7

If you are willing to commit to a compiler with GNU extensions, it's possible. So be warned.

It takes two overloads and a helper macro:

template<std::size_t N>
constexpr void assert_helpr(int(&)[N]) = delete;

void assert_helpr(...) {}

#define my_static_assert(...) do {               \
    __extension__ int _tmp [(__VA_ARGS__) + 1];  \
    assert_helpr(_tmp);                          \
} while(0)

It works as follows:

  1. The macro grabs your token soup and treats it like an integral expression. +1 is to avoid pathological zero cases.
  2. If the expression is a constant expression, we get a standard array. Otherwise we get a VLA.
  3. Now we call one of two overloaded functions. Overload resolution will always choose a c var-arg function last. If what we got was a regular array, the first, deleted overload is chosen, and the assertion fails.
  4. If we got a VLA, the second overload is chosen and the assertion passes.

I haven't tested it thoroughly, but it seems to work on both Clang and GCC. See it live.


@Artyer was kind enough to share a solution based on this approach on godbolt.

Here's a reduction of that code to this problem:

template<std::size_t N>
constexpr std::false_type assert_helpr(int(&)[N]);
constexpr std::true_type  assert_helpr(...);

#define my_static_assert(...) do {                               \
    __extension__ int _tmp[(__VA_ARGS__) + 1];                   \
    static_assert(decltype(assert_helpr(_tmp)){}, #__VA_ARGS__); \
} while(0)

Same use of overloading, except this time we grab an actual result type from the call via decltype, and proceed to create a true compile time Boolean constant out of it.

This allows direct use of static_assert, and as a nice to have feature, we can pass it the stringified token soup to get an indication in the error of the expression that failed.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458