在静态断言和运行时错误之间自动选择

时间:2017-03-07 13:00:07

标签: c++ c++11 visual-c++ visual-studio-2015 static-assert

我有一个执行除法并检查对齐的宏。

#define BYTES_TO_WORDS(x)  ((CHECK_ALIGNMENT(x,2)) * ((x) / 2))

我希望将CHECK_ALIGNMENT实现为始终返回1的宏,并在x未除以2时触发错误。
BYTES_TO_WORDS是从不同的上下文调用的,有时使用x作为编译时常量整数表达式,有时使用x作为在运行时解析的整数表达式。

是否可以实现CHECK_ALIGNMENT,以便在使用常量表达式调用宏时执行static_assert,并在表达式不是编译时常量时执行某些运行时错误检查?
我可以更改宏定义,但不能更改宏的调用和使用方式。

这是一个可能的解决方案(并不总是有效):

#define CHECK_ALIGNMENT(x,alignedTo) (1/(((alignedTo)-((x)%(alignedTo)))/(alignedTo)))

在此实现中,我们应该在运行时或编译时获得除以零错误,具体取决于输入。
但是,由于compiler bug,这并不总是有效。 此外,错误消息不是很好。

更好的解决方案是确定参数是否是编译时常量,并在这种情况下使用static_assert,并使用良好的编译时错误消息。如果参数不表示编译时常量,则检查运行时的对齐情况。

这可能吗?
我需要这个在Visual Studio 2015上工作。

澄清

在评论中有一些关于为什么我在C ++问题中使用宏的讨论 BYTES_TO_WORDS宏位于头文件中,包含在各种工具中,C ++编译器就是其中之一 其他工具使用此宏并评估算术表达式((x) / 2),但在这些工具上,我将CHECK_ALIGNMENT定义为1,因为它们无法处理constexpr,模板甚至函数调用。 /> 使用C ++编译器编译此标头时,我想将CHECK_ALIGNMENT定义为其他内容,在需要时会触发static_assert或运行时错误。
CHECK_ALIGNMENT的定义可以是任何C ++ 11代码(或VS2015支持的C ++ 14),可以使用模板,constexpr或其他。

2 个答案:

答案 0 :(得分:0)

我能提出的唯一解决方案要求您在使用它们提供的宏之前包装编译时常量。

因此,“自动”下面的代码会在编译时或运行时错误之间进行选择,只要您记得通过包装它们来正确指示编译时常量。请注意,由于转换运算符,包装器也会自动转换为普通的int

另外,在使用我为概念验证实现创建的Constant类之前,请务必先查看Boost.Hana。下面介绍的实现不是通用的(它只包装int并且仅转换回int)并且严重缺乏功能。

#include <iostream>
#include <stdexcept>

template <int I>
struct Constant
{
    constexpr Constant() = default;

    constexpr operator int () const
    {
        return I;
    }
};

template <int I = 0>
constexpr int check_alignment(Constant<I>)
{
    static_assert(I % 2 == 0, "Error: not divisible by 2!");
    return 1;
}

int check_alignment(int value)
{
    if (value % 2 != 0)
        throw std::runtime_error("Error: not divisible by 2!");

    return 1;
}

#define CHECK_ALIGNMENT(x) check_alignment(x)
#define BYTES_TO_WORDS(x) ((CHECK_ALIGNMENT(x)) * ((x) / 2))

int main()
{
    constexpr Constant<2> c2;
    constexpr Constant<3> c3;
    constexpr Constant<4> c4;

    // Compile-time checks:
    static_assert(BYTES_TO_WORDS(c2) == 1, "Error!");
    static_assert(BYTES_TO_WORDS(c2) == 2, "Error!"); // "Error!"
    static_assert(BYTES_TO_WORDS(c3) == 2, "Error!"); // "Error: not divisible by 2!"
    static_assert(BYTES_TO_WORDS(c4) == 2, "Error!");
    static_assert(BYTES_TO_WORDS(c4) == 4, "Error!"); // "Error!"

    // Runtime checks:
    std::cout << BYTES_TO_WORDS(2) << std::endl; // outputs "1"
    std::cout << BYTES_TO_WORDS(3) << std::endl; // "Runtime error: Error: not divisible by 2!"
    std::cout << BYTES_TO_WORDS(4) << std::endl; // outputs "2"

    return 0;
}

答案 1 :(得分:0)

modern C++ solution (by SO user oliora, not by me)用于在可能的情况下发出编译错误,否则会出现运行时错误,我将在此复制:

// A compilation of the following posts:
// https://stackoverflow.com/questions/18648069/g-doesnt-compile-constexpr-function-with-assert-in-it
// http://ericniebler.com/2014/09/27/assert-and-constexpr-in-cxx11/
#include <cassert>
#include <utility>

template<class Assert>
inline void constexpr_assert_failed(Assert&& a) noexcept { std::forward<Assert>(a)(); }

// When evaluated at compile time emits a compilation error if condition is not true.
// Invokes the standard assert at run time.
#define constexpr_assert(cond) \
((void)((cond) ? 0 : (constexpr_assert_failed([](){ assert(!#cond);}), 0)))

定义了一个constexpr_assert,您可以使用它创建一个函数:

template<typename T> constexpr
void check_alignment(T x, short alignTo) {
    constexpr_assert(x % alignedTo == 0);
}

旁注:由于这似乎是用于检查最右边的 n 位为0的实用程序,我可能采取不同的方法:

template<typename T> constexpr
void assert_rightmost_zeroes(T x, short zero_bits)
{
    auto shifted = x >> zero_bits;
    shifted = x << zero_bits;
    constexpr_assert(x == shifted);
}

然后,对于必须在BYTES_TO_WORDS表达式中使用的实际宏,我将使用#ifdef来确保您有两个版本,其中一个版本适用于您的非C ++工具:

#ifdef __cplusplus
// Create and invoke a lambda to turn a series of statements into a single expression
#  define CHECK_ALIGNMENT(x, alignTo) [](auto x, auto alignTo){ check_alignment(x, alignTo); return 1; }(x, alignTo)
#else
// Use the solution that doesn't always work and has an ugly error message
#  define CHECK_ALIGNMENT(x, alignedTo) (1/(((alignedTo)-((x)%(alignedTo)))/(alignedTo)))
#endif

(当然,您可以保留原来的策略,即从check_alignment函数返回1,这将消除对lambda技巧的需要;或者,您可能只有{{1}的两个不同定义},其中一个是函数,另一个是宏。)

有关BYTES_TO_WORDS和其他几种方法的详细信息,请参阅this blog postthis old answer of mine