问题
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