The GCC __attribute__((pure))
and __attribute__((const))
allow functions to be declared as non–side-effecting and referentially transparent, respectively; let's say I want to write pure_assert
and const_assert
macros, whose argument must be an expression of the appropriate level of strictness, that is:
assert(oops_a_side_effect());
Silently results in different behaviours in debug and release, but:
pure_assert(oops_a_side_effect());
const_assert(oops_read_a_global());
Would be a compile-time error, at least in debug builds. For what I should hope are obvious reasons, you can't just create a pure_assert_impl
declared __attribute__((pure))
and have the macro expand to it. So is it possible to write these macros?
Does gcc enforce that pure and const functions cannot call non-pure or non-const functions, respectively? If so, you could define a properly attributed function template which takes a function pointer as a template parameter, and let the macro expand to an invocation of that function template. I assume you need to support functions that take parameters, this would work better with C++0x variadic templates or lambdas.
gcc does not enforce purity or referential transparency in any way. These attributes are just hints for the optimiser. So the answer is no.
I doubt that there's a really good solution for this, but I've found a possibility for something like const_assert()
, which appears to work with the two versions of gcc I have readily to hand (4.1.2 and 4.4.4).
#include <assert.h>
extern void dummy_void_function(void);
#define const_assert(x) \
assert(__builtin_choose_expr(__builtin_constant_p((x) == (x)), \
(x), dummy_void_function()))
extern int function(void);
extern int __attribute__((pure)) pure_function(void);
extern int __attribute__((const)) const_function(void);
extern int global;
extern volatile int volatile_global;
void test(int arg)
{
/* These cause compile-time errors: */
const_assert(function() == 0);
const_assert(pure_function() == 0);
const_assert(volatile_global == 0);
/* These don't: */
const_assert(const_function() == 0);
const_assert(arg == 0);
const_assert(global == 0);
}
This is really checking whether the expression (x) == (x)
is regarded as a compile-time constant by the compiler, and creating brokenness if it's not. The "good" cases effectively become assert(x);
and the bad ones generate compile-time errors:
$ gcc -c const_assert.c
const_assert.c: In function 'test':
const_assert.c:18: error: void value not ignored as it ought to be
const_assert.c:19: error: void value not ignored as it ought to be
const_assert.c:20: error: void value not ignored as it ought to be
However, with optimisation enabled, it still produces errors in the expected cases, but one of them is a bit odd:
$ gcc -O -c const_assert.c
const_assert.c: In function 'test':
const_assert.c:18: error: void value not ignored as it ought to be
const_assert.c:19: error: first argument to '__builtin_choose_expr' not a constant
const_assert.c:20: error: void value not ignored as it ought to be
...you'd expect the result of __builtin_constant_p()
to be regarded as a constant by definition! So I'm not sure that I would really trust this for real code...
(And I don't have any good ideas right now for pure_assert()
!)
Tough luck, I'm afraid. Macros are expanded by the preprocessor, even before the compiler starts looking at the code.
What maybe could be a solution, is to let the assert test expression be evaluated regardless of release or debug mode, however let the result be tested only in debug mode.
精彩评论