C++ modern but non-existent code

The “crown jewel” of the post first. I use standard C++ and this macro (gasp!):

Any number of any legal arguments works. In or out of functions. In or out a namespace.

That is runtime zero cost. noexcept is compile time affair. And since the result is cast to “nothing” aka void that compiles to exactly 0 bytes.

The philosophy

What is “non-existent” code?  That is the code that is compiled into zero bytes. It is non-existent in the resulting executable.

Why would anybody do this? To check is something “compiles” but not use it yet. Or ever. If some code is legal. Is some type is legal, etc. Also, you might want it back sometime in the near future?

Well, that is simple, say you, there is this well known, mature (decades old) 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: We are not in 1990 any more, why making some code “used but unused” in standard C++ code, while in the same time compiled in modern C++ compilers, these days?

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

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

// implement latter
}

On seeing this, slightly older compiler screams 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] ;
// pacify my compiler

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 warnings about unused things?
    • For historical reasons
    • To help you not have unused things in developed code
    • Some of them must be used but you forgot to use them

Modern compilers warn on this just if asked to, that is if warning level is above default. In case of MSVC the default warning level is: /W3

The modern compiler automatically replaces the unused code with zero bytes. You do not need to do anything.

And now the big one.

Ok and fine. why don’t we just use this “de facto” standard macro UNUSED, then? Here is why. The key issue.  The statement:

(void)x ;

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

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

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 evaluations,  behind an UNUSED macro, please.

A resurrection of countless bugs

Often 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. Consider this.

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

   UNUSED(a1);
UNUSED(retval);
// deveoper before you has put the following comment
//
// here we will have to use the logger
// LOG( function, a1, retval)
}

The developer above, wanted to “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 royal pain to comment that thing in, later down the time-line, have the syntax error, and then fix, debug 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 so on…

In real life software development (thousands of functions) this kind of “we will fix it after commenting it back in” event, is always a real risk. And a naturally rich source of a resurrection 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

This is in addition to my #UNUSED on the top of this post. This here, is standard C++ with (variadic) macro.

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.

And most importantly, static_assert() make is possible this can be placed anywhere.  in the global space, in the namespace, in the function in the class, just anywhere. That is: in any scope.

Almost all existent  C “UNUSED” macros simply cannot be placed in any scope, but only where the macro expansion will produce properly rendered qualified or 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, code over there is almost all, just dropped in the global scope.  DBJ_REMOVE can be placed anywhere and it takes “almost 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 bytes overhead, without evaluating the expressions ‘removed’.

And remember: it will not allow you to leave non compilable  code behind.

Feel free to play with Godbolt over there. 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:  compiler first compiles the things (it evaluates the things) and then does not complain if they are not used.

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

Thus, I might not joyfully use this everywhere. But you might, sometimes and somewhere.

In any case, enjoy the standard C++. Do not build unsafe code. Even if macro is the solution.

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.