C++ How to make unwanted code visible but non-existent

C++ How to make unwanted code visible but non-existent?  Huh, what is “non-existent”?  That is: it is compiled into zero bytes. It is non-existent in the resulting executable.

Ok, but, you might want it back sometime in the near future?  Well, that is simple, say you, there is this well known, mature and universally used macro that does make unused code “get out of the way”

// de-facto standard
#define UNUSED(x) (void)x

If this macro makes you ask questions please read on, otherwise, leave in peace.

First question (I have) is why making some code “unused” in standard C++ code, compiled in modern compilers these days?

Because, some of them still throw this annoying “unused variable… ”  or “unused parameter …” , whenever (for example) you are writing the code and you still have not fully developed innards, but just interfaces.

std::string clever_function
( int a1, bool a2, float a3 ) {
std::string retval_ ;
}

On seeing this, slightly older compiler scream at you about unused arguments and variables. You dutifully oblige and you sprinkle your code with the famous UNUSED macro or some such macro, available in your DEVENV environment.

char * clever_function ( int a1, bool a2, float a3 ) {
static char retval_[BUFSIZ] ;

UNUSED(a1);
UNUSED(a2);
UNUSED(a3);
UNUSED(retval);
}

The questions and observations about the above:

  • In the next development cycle, you or someone else will have to remove all these UNUSED statements
  • Why is compiler throwing warning about unused things?
    • For historical reasons
    • To help you not have unused things in developed code
  • Modern compilers warn on this just if asked to, that is if warning level is above default.
    • In case of MSVC the default is /W3
  • The modern compiler automatically replaces the unused code with zero bytes.

And now the big one.  The key issue.  The statement:

(void)x ;

Evaluates the ‘x’ before casting it to void.  So if you just use this in your standard C++ code, you might be evaluating all sorts of functions calls, instantiations or whatever, before casting them to (void).

// standard C++ code
// what is x?
(void)x;

In the case of C++ type of x above, can be any fundamental, compound or UDT (user-defined type). How do you know what is the side effect of (void)x ;  if a type of x is a global instance of some complex type from some external library?

The last thing you want in your code is silent evaluations and silent side effects as direct consequences of that.

In this day and age, the best course of action is to simply use the standard modern C++ compiler and let it just remove the unused code. Do not hide the things behind an UNUSED macro, please.

Important note.

// this is not a syntax error
if ( whatever ) value ;
// maybe you wanted to return that value?
// if (whatever) return value ;

If the compiler is set to warn on unused values it will warn you on the above, otherwise, it won’t. You have been warned.

What if you actually do want to see the code that is going to be required but you do not want it in action just yet? You want to see it, but you do not want it to get in the way.

// standard C++ code
return_type clever_function
( arg_type_1 a1 ) {
some_library_type retval_[BUFSIZ] ;

   UNUSED(a1);
UNUSED(retval);
// here we will have to use the logger
// probably like so
// LOG( __function__, __FILE__, __LINE__)
}

Just write it and comment it out? Perhaps. That is not good because it will inevitably hide syntax mistakes and a such.  It will be a pain to comment thing in, later down the line and then retype them, debug them and a such.

What if for the LOG  call above, the order of arguments is wrong? Well, someone else (or is it you?), will find out after that line is commented in.  And by the way it is not ‘__function__’ but ‘__func__’. And so on…

In real life software development (thousands of functions) this kind of “we will fix it after commenting it back in” event, can be a real risk. And a naturally rich source of countless bugs afterwards.

It is much better to check immediately if something compiles, even if it is not immediately required.

I have written a little macro that helps

That is standard C++. Not C. Standard C++  noexcept operator does not evaluate its argument. And there are no exceptions to that and no legacy issues like with the sizeof(x) operator that many of you would instinctively use.

Importantly, static_assert() can be placed anywhere too.  in the global space, in the namespace, in the function in the class, just anywhere. That is: in any scope.

That is the reason why almost all “UNUSED” macros simply cannot be placed in any scope, but only where the macro expansion will produce properly rendered qualified and unqualified, standard C++names. Like the semi-famous MSVC UNREFERENCED_PARAMETER(P) macro.  As its name says it is made for unreferenced parameters. To be used in the function scope.

Note: What are “unqualified names” or “qualified names”? If curious read on the subject here. That means you can not just willy-nilly place these “de-facto standard” macros wherever required. That will simply not compile.

To see DBJ_REMOVE in action please study the online proof of concept.

You will notice immediately, it is almost all, just in the global scope.  DBJ_REMOVE can be placed anywhere and it takes anything. Crucially, as long as it is normal and proper C++ code.  Any variable or expression will have to be a proper C++. And it leaves zero byte overhead, without evaluating the expressions ‘removed’.

Feel free to play with that online IDE (Godbolt). Include any standard header … Try it with anything you like, function calls, array index moves, pointer arithmetics or whatever else you fancy, and observe the resulting assembly on the right. It is zero.

The “standard” UNUSED kind-of-a macro will be possible to use just in some limited amount of use cases (definitely not in the global scope) on its own. And it will sadly, always evaluate its argument.

[[maybe_unused]]

Is the standard C++ attribute that suppresses the warnings on unused entities.  Where you by now immediately see the crucial point: it evaluates the things.

Sometimes you might need that. In that case, use the standard attribute. But beware, it has somewhat cumbersome placement rules. And sometimes it is simply unclear where to place it.

// where to place [[maybe_unused]] in this case?
auto [a,b,c] = make_compound_type () ;
auto d = a + b;
// this will not compile
// [[maybe_unused]]c ; 

// let's just use our macro
// this will make it obvious but will be
// a zero byte overhead *and*
// will not evaluate the c
DBJ_REMOVE(c);

Enjoy the standard C++. Do not build unsafe code.

And what about C?

C, what C? Did not Bjarne say C is obsolete?

Wait! Stop! Only kidding 🙂 C might be obsolete but the amount of (mission critical) C code in action on this planned is staggering. So, I have to oblige.

There are C macros made for the same purpose but better than the “de-facto standard” (void)x . As an example here is one from the good UCRT team as present in the latest SDK we have today in the file corecrt.h

// Windows SDK 10.0.17134.0
// corecrt.h

// #define _STATIC_ASSERT(expr) typedef char __static_assert_t[(expr) != 0]

I am sure you can see the trick used and you can also, as trained by now, can see the flaw: expr is being evaluated.

to cut the (very) long story (very) short I have seen loads of macros for C for the same purpose. For C99, C11 and so on. And yes this is indeed the best trick. With the slight variation form me:

// this is C
#if ! defined(__cplusplus)
#define DBJ_REMOVE(expr) typedef char __static_assert_t[sizeof(expr) != 0] #endif

Yes with the little help of the good old sizeof() operator expr is not being evaluated.

For C this is the pretty good solution. Covering all the C standards in use today too.  Although not perfect. If interested on the caveats perhaps this is the good starting point.

As I already said: Enjoy the standard C++. Do not build unsafe code.

Do not leave unsafe C++ builds.

Do not leave unsafe C++ builds.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.