Automatically selecting between static assert and runtime error

女生的网名这么多〃 提交于 2019-12-10 13:48:19

问题


I have macro that performs a division and checks alignment.

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

I would like to implement CHECK_ALIGNMENT as a macro that always returns 1, and triggers an error if x does not divide by 2.
The macro BYTES_TO_WORDS is called from different contexts, sometimes with x as a compile-time constant integer expression and other times with x as an integer expression that is resolved on runtime.

Is it possible to implement CHECK_ALIGNMENT such that it would perform static_assert when the macro is called with constant expression, and some runtime error check when the expression is not a compile-time constant?
I can change the macro definitions, but not the way the macro is called and used.

Here is a possible solution (that doesn't always work):

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

In this implementation we should get Division By Zero error on either runtime or compile time, depends on the input.
However this does not always work due to a compiler bug. Also, the error message is not very nice.

A better solution would be identifying whether the parameter is a compile time constant and using static_assert in such case, with a nice compile time error message. If the parameter does not represent a compile time constant, then check the alignment on runtime.

Is this possible?
I need this to work on Visual Studio 2015.


Clarification

There are some discussions in the comments regarding to why I'm using macros in C++ question.
The BYTES_TO_WORDS macro is in a header file which is included by various tools, C++ compiler is one of them.
Other tools use this macro and evaluate the arithmetic expression ((x) / 2), but on these tools I define CHECK_ALIGNMENT to 1 as they are not capable of handling constexpr, templates or even function calls.
When compiling this header with C++ compiler I would like to define CHECK_ALIGNMENT to something else, that would either trigger static_assert or runtime error when needed.
The definition of CHECK_ALIGNMENT can be any C++11 code (or C++14 that is supported by VS2015), can use templates, constexpr or whatnot.


回答1:


The only solution I can come up with requires you to wrap your compile-time constants before using them in the macros you provided.

So the code below "automatically" selects between a compile-time or runtime error as long as you remember to properly indicate compile-time constants by wrapping them. Note that the wrapper also automatically converts to a plain-old int thanks to the conversion operator.

Also, be sure to take a look at Boost.Hana before using the Constant class I created for the proof of concept implementation. The implementation presented below is not generic (it only wraps int and only converts back to int) and severely lacking in functionality.


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



回答2:


There is a modern C++ solution (by SO user oliora, not by me) for giving a compile error when possible and a runtime error otherwise, which I will simply copy here:

// 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)))

One constexpr_assert is defined, you can create a function using it:

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

Side note: since this appears to be a utility for checking that the rightmost n bits are 0, I might take a different approach:

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

Then, for your actual macro that must be be usable in the BYTES_TO_WORDS expression, I'd use an #ifdef to ensure that you have two versions, one of which works with your non-C++ tools:

#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

(Of course, you could keep your original strategy of just returning 1 from the check_alignment function, which would eliminate the need for the lambda trick; alternatively, you could just have two different definitions for BYTES_TO_WORDS, one of which is a function, the other of which is a macro.)

For more details about constexpr_assert and several alternate approaches, see this blog post and this old answer of mine.



来源:https://stackoverflow.com/questions/42649007/automatically-selecting-between-static-assert-and-runtime-error

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!