如果在编译时未知表达式,则通过静态断言

时间:2019-05-01 13:11:21

标签: c++

我要实现的my_static_assert与c ++ 17单参数static_assert略有不同:如果在编译时my_static_assert 内部的条件未知 ,它应该通过

以下示例中的第二个my_static_assert应该通过,但是如果我使用static_assert,它将失败。

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

如果您想知道为什么会出现这个问题:

我正在使用叶函数f1,f2,f3,f4和在表达式节点上的操作来构建表达式:sum,mul,div,sub。在编译时已知的叶子包含始终为0的值。

我正在尝试检查我的表达式是否包含至少一个在编译时未知的元素。

1 个答案:

答案 0 :(得分:7)

如果您愿意使用具有GNU扩展名的编译器,则可以。所以要警告。

这需要两个重载和一个辅助宏:

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)

它的工作原理如下:

  1. 宏会捕获您的令牌汤,并将其视为一个整数表达式。 +1是为了避免病理性零病例。
  2. 如果表达式是一个常量表达式,我们将得到一个标准数组。否则,我们将获得VLA。
  3. 现在我们调用两个重载函数之一。重载解析将始终最后选择一个c var-arg函数。如果我们得到的是常规数组,则选择第一个已删除的重载,并且断言失败。
  4. 如果获得了VLA,则选择第二个重载,并且断言通过。

我还没有对它进行彻底的测试,但是它似乎在Clang和GCC上都可以使用。 See it live


@Artyer非常友好,可以在godbolt上分享基于此方法的解决方案。

以下是该代码的reduction

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)

与重载的用法相同,除了这次我们通过decltype从调用中获取实际的结果类型,然后继续从中创建一个真正的编译时布尔常量。

这允许直接使用static_assert,作为一项不错的功能,我们可以将其传递给字符串化的令牌汤,以指示失败的表达式错误。