Testing if an expression has side effects at compile time

Alejandro Colomar alx@kernel.org
Fri Jan 9 15:58:30 GMT 2026


Hi Chris,

On Fri, Jan 09, 2026 at 03:39:28PM +0000, Christopher Bazley wrote:
> 
> Hi Alex,
> 
> On 09/01/2026 12:07, Alejandro Colomar via Gcc wrote:
> > Hi!
> > 
> > I'm trying to find a way to verify that an expression (usually passed as
> > an argument to a macro) has no side effects.  Is this possible?
> > 
> > Let's say I want to implement strnul():
> > 
> > 	#define strnulA(s)  (s + strlen(s))  // Problem: evaluates twice
> > 
> > 	#define strnulB(s)        \
> > 	({                        \
> > 		auto  s_ = s;     \
> > 		s_ + strlen(s_);  \
> > 	})
> > 
> > The problem with strnulB() is that it has a local variable 's_'.  This
> > means I can't pass an argument called s_.  That is, the following call
> > doesn't work:
> > 
> > 	strnulB(s_);
> > 
> > Is there a way to implement something like this?:
> > 
> > 	#define strnulC(s)                           \
> > 	({                                           \
> > 		static_assert(!has_side_effects(s)); \
> > 		s + strlen(s);                       \
> > 	})
> 
> I think it would be impossible to implement such a feature perfectly because
> the expression 's' might invoke functions that are defined in other
> translation units (even source code that hasn't been written yet). Any
> implementation would need to be conservative.
> 
> > If there's no way, it would be interesting to add compound literals of
> > function type to achieve this:
> > 
> > 	#define strnulD(...)                                          \
> > 	((static inline auto                                          \
> > 	  (auto s))                                                   \
> > 	{                                                             \
> > 		return s + strlen(s);                                 \
> > 	}(__VA_ARGS__))
> > 
> > Which guarantees that the argument is evaluated once.
> 
> I don't favour statement literals precisely because they provide a mechanism
> for creating fake encapsulation. The 'fakeness' comes from the point that
> you raised: even if the programmer diligently assigns each macro argument to
> a local variable (which is a pain in itself), all of the identifiers in the
> enclosing scope are still in scope.
> 
> I would solve the same problem like this, except that I probably wouldn't
> bother with the templates for such a trivial function:
> 
> #define STRNUL_TEMPLATE(NAME, QCHAR) \
> static inline QCHAR *NAME(QCHAR *s) \
> { \
>     return s + strlen(s); \
> }
> 
> STRNUL_TEMPLATE(strnulc, const char)
> STRNUL_TEMPLATE(strnul, char)
> 
> #define STRNUL(S) \
>   _Generic(S, \
>            char const *: strnulc, \
>            char *: strnul)(S)

It can be done simpler than this.  I've implemented the QChar APIs from
libc as simple wrappers around the old functions:

	#define __QVoidptrof(p)  typeof(1?(p):(void*){0})
	#define __QCharptrof(s)  typeof                               \
	(                                                             \
	       _Generic((__QVoidptrof(s)){0},                         \
		       const void *: (const char *) 0,                \
		       void *:       (char *) 0                       \
	       )                                                      \
	)

	define strchr(s, chr)  ((__QCharptrof(s)) strchr(s, chr))

Essentially the same could be done with strnul() by just adding the
function definition:

	#define strnul(s)  ((__QCharptrof(s)) strnul(s))

	char *
	(strnul)(const char s)
	{
		return s + strlen(s);
	}

But I was hoping I could do it even simpler.  A function taking and
returning 'auto' would certainly be simpler.  Maybe I'm trying to
simplify too much, though.

> 
> int main(void)
> {
>     char s[] = "Mutable",
>          *ps = STRNUL(s);
>     const char cs[] = "Immutable",
>                *pcs = STRNUL(cs);
>     return 0;
> }
> 
> I do support function literals as a more scenic alternative to lambdas, but
> I don't believe the proposal currently before WG14
> (https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3679.pdf) incorporates
> the idea of return type inference. I'd expect typeof
> to be used for that purpose instead:
> 
> 	#define STRNUL(S) \
> 	(static typeof(S) (typeof(S) s)) \
> 	{ \
> 		return s + strlen(s); \
> 	}(S)
> 
> Or maybe, in future C:
> 
> 	#def STRNUL(S)
> 	(static typeof(S) (typeof(S) s))
> 	{
> 		return s + strlen(s);
> 	}(S)
>         #enddef
> 
> Note that typeof never evaluates its operand and it is much simpler than C++
> templates.

Have a lovely day!
Alex

> 
> -- 
> Christopher Bazley
> Staff Software Engineer, GNU Tools Team.
> Arm Ltd, 110 Fulbourn Road, Cambridge, CB1 9NJ, UK.
> http://www.arm.com/

-- 
<https://www.alejandro-colomar.es>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <https://gcc.gnu.org/pipermail/gcc/attachments/20260109/ded96f2a/attachment.sig>


More information about the Gcc mailing list