Jonathan Boccara's blog

How to Disable a Warning in C++

Published August 30, 2019 - 0 Comments

As explained in item 53 of Effective C++, you should “Pay attention to compiler warnings”. In the vast majority of cases, the compiler has a good reason to emit them, and in the vast majority of cases, they point out to an oversight in your code.

But in a minority of cases, you may want to deliberately write code that triggers a warning.

In such occasions, letting the warning in the compiler’s output has several drawbacks. First, you will no longer have a clean build with no errors and no warnings. This warning will always remain here, and you’ll have to check that it’s the one you decided to leave in every time you compile the code.

This doesn’t scale if there are several warnings you decide to leave, because at each build you’ll have to check them all to see if a new warning hasn’t popped up and needs to be checked.

Second, if you’re following the best practice of transforming warnings into errors, by activating the -Werror flag in gcc and clang for example, leaving a warning in is simply not an option.

Fortunately, C++ lets you block the emission of a specific warning for a portion of code. Let’s see how to do that and keep code expressive.

Different code for different compilers

Let’s take the example of the warning that warns you that you didn’t use one of the parameters of a function:

void f(int a, int b)
{
    std::cout << a << '\n';
    // we are not using b!
}

The compiler is able to emit a warning for this. But all compilers don’t emit the same warning.

Here is gcc’s warning, which is the same as clang’s:

warning: unused parameter 'b' [-Wunused-parameter]

And here is Visual Studio’s warning:

warning C4100: 'b': unreferenced formal parameter

You can observe that they don’t have the same text and–more importantly for our purpose–the warning is not identified the same way.

Visual Studio identifies warnings with a number (here, 4100), whereas gcc and clang use a string (here, -Wunused-parameter).

As you can imagine, that will lead to different code to disable the same warning between the compilers.

We’re going to see how to disable a warning on gcc, clang, and on Visual Studio, and in case your application has to compile on all three, how to write code that disable a warning on all compilers.

The disabling sequence

Before we get into the code for each compiler, there is something in common in the sequence of disabling a warning between all three compilers.

To disable a set of warnings for a given piece of code, you have to start with a “push” pre-processor instruction, then with a disabling instruction for each of the warning you want to suppress, and finish with a “pop” pre-processor instruction.

For example, in our case, the sequence would look like that:

// up until this line, the warning is active

// PUSH disable warning (instruction specific to the compiler, see below)
// DISABLE the warning that a parameter is not used

void f(int a, int b)
{
    std::cout << a << '\n';
    // we are not using b, but the compiler won't emit a warning
}

// POP disable warning, the warning is now active again

Now let’s dive into the code for each compiler.

Disabling a warning on gcc and clang

A good thing is that gcc and clang require the exact same code for disabling a warning, as far as I’m aware.

The push instruction is this:

#pragma GCC diagnostic push

Note that even though it says “GCC”, it also works for clang.

The pop instruction is this:

#pragma GCC diagnostic pop

And to disable a warning, you indicate it this way:

#pragma GCC diagnostic ignored "-Wunused-parameter"

Putting this together, to suppress the warning in our example code we write:

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"

void f(int a, int b)
{
    std::cout << a << '\n';
    // we are not using b!
}

#pragma GCC diagnostic pop

That’s is for gcc and clang.

Disabling a warning on Visual Studio

With Visual Studio, the push instruction is this:

#pragma warning( push )

The pop instruction is this:

#pragma warning( pop )

And to disable a specific warning, we need to write code like this:

#pragma warning( disable : 4100 )

Remember, in Visual Studio warnings are identified with numbers, not names.

If we have to suppress the warning in our example code on Visual Studio, we would write this:

#pragma warning( push )
#pragma warning( disable : 4100 )

void f(int a, int b)
{
    std::cout << a << '\n';
    // we are not using b!
}

#pragma warning( pop )

All in all, this is not so complicated.

But what if you write code that needs to compile on gcc, clang AND Visual Studio?

That can happen if your application is deployed on multiple OSes, or if you write a library for the general population of C++ programmers.

This is where the fun begins.

Disabling a warning on gcc, clang and Visual Studio at the same time

Since disabling warnings is done at the level of the pre-processor, we’re going to need a macro. We need to write a macro that resolves to either one of the above pieces of code, depending on the compiler used.

The disabling sequence is similar between all three compilers, so we’ll write a macro for each of the three steps: push, disable and pop:

DISABLE_WARNING_PUSH
DISABLE_WARNING_UNREFERENCED_FORMAL_PARAMETER

void f(int a, int b)
{
    std::cout << a << '\n';
    // we are not using b!
}

DISABLE_WARNING_POP

Let’s see how to write each macro on the various compilers, and then how to write code to combine all this into a cross-compiler code. We’re going to have some macro fun.

Implementing the macros on gcc and clang

As we saw above, the push in gcc and clang is this:

#pragma GCC diagnostic push

Our first instinct could be to define the DISABLE_WARNING_PUSH like this:

#define PIPES_DISABLE_WARNING_PUSH     #pragma(GCC diagnostic push)

But using DISABLE_WARNING_PUSH then fails to compile:

error: expected unqualified-id
DISABLE_WARNING_PUSH
^
note: expanded from macro 'DISABLE_WARNING_PUSH'
#define DISABLE_WARNING_PUSH #pragma(GCC diagnostic push)

It’s because we’re not allowed to use #pragma in a #define instruction.

To circumvent this problem, compilers commonly offers a “pragma operator”, that is not standard and differs across compilers.

In gcc and clang, it is called _Pragma, and can be used this way:

#define DISABLE_WARNING_PUSH _Pragma("GCC diagnostic push")

Note that _Pragma expects a string with quotes, hence the "GCC diagnostic push".

Similarly, the pop instruction is this:

#define DISABLE_WARNING_POP _Pragma("GCC diagnostic pop")

Now to disable the warning, we have to write this:

#define DISABLE_WARNING_UNREFERENCED_FORMAL_PARAMETER   _Pragma("GCC diagnostic ignored \"-Wunused-parameter\"")

Note the \" around the name of the warning. Remember that gcc and clang identify warning with strings, and that _Pragma expects a string. This results in a string within a string, so quotes inside of quotes, which then need to be escaped.

This is not pretty. To mitigate this, we could use C++11’s raw strings literals:

#define DISABLE_WARNING_UNREFERENCED_FORMAL_PARAMETER _Pragma(R"(GCC diagnostic ignored "-Wunused-parameter")")

But this is still far from ideal. Especially if we want to disable several types of warnings, because we’d need to repeat this code over and over.

What would be nice would be to write this:

#define DISABLE_WARNING_UNREFERENCED_FORMAL_PARAMETER     DISABLE_WARNING(-Wunused-parameter)

With no quotes, just the name of the warning.

Let’s see how to do that. This where the macro fun begins.

Generic macro code

To get rid of all the issues with quotes, we’re going to use the “Stringizing opeator”, which is #. As Microsoft Docs puts it, “If [the stringizing opeator] precedes a formal parameter in the macro definition, the actual argument passed by the macro invocation is enclosed in quotation marks and treated as a string literal.”

Put another way, the # operator puts quotes around a macro parameter.

The stringizing operator helps support the DO_PRAGMA trick, that consists in defining the following macro:

#define DO_PRAGMA(X) _Pragma(#X)

In short, DO_PRAGMA puts quotes around a string and passes it to the _Pragma operator.

We’re going to use it this way (we’ll see how that works step by step afterwards):

#define DISABLE_WARNING(warningName) \
    DO_PRAGMA(GCC diagnostic ignored #warningName)

DISABLE_WARNING is a macro function that takes a parameter, which we can invoke like this:

DISABLE_WARNING(-Wunused-parameter)

In this case, warningName is -Wunused-parameter. So #warningName, with the stringizing operator, is "-Wunused-parameter".

Thus,

GCC diagnostic ignored #warningName

is equivalent to

GCC diagnostic ignored "-Wunused-parameter"

Finally, DO_PRAGMA(GCC diagnostic ignored #warningName) puts quotes around all that and sends it to _Pragma. Which leads to the desired result.

As a result, this macro function allows to disable several warnings with expressive code:

#define DISABLE_WARNING_UNREFERENCED_FORMAL_PARAMETER       DISABLE_WARNING(-Wunused-parameter)
#define DISABLE_WARNING_UNREFERENCED_FUNCTION               DISABLE_WARNING(-Wunused-function)
// and so on

Implementing the macro in Visual Studio

If you came out of the preceding section in one piece, the rest should glide.

Visual Studio follows the same principles as gcc and clang: you can’t put a #pragma inside of a #define directive, but there is a pragma operator to help us achieve this. But contrary to gcc, it is not called _Pragma but __pragma, with two underscores.

What’s easier in Visual Studio than in gcc and clang is that the warnings are not identified by strings but by numbers (e.g. 4100), and the __pragma operator doesn’t expect strings in quotes.

So here is how to write DISABLE_WARNING for Visual Studio:

#define DISABLE_WARNING(warningNumber)    __pragma(warning( disable : warningNumber ))

The push and the pop are also straightforward:

#define DISABLE_WARNING_PUSH __pragma(warning( push ))
#define DISABLE_WARNING_POP __pragma(warning( pop ))

Putting it all together

Now that we know how to disable a warning for gcc, clang and Visual Studio, let’s put this altogether in the same code, so that your application or library can run on all three compilers with the same code.

Essentially, the code is going to follow this structure:

if Visual Studio
    code for Visual Studio
else if gcc or clang
    code for gcc and clang
else
    macros that are defined but don't do anything

To identity the compiler, we can rely on the specific macro that each of them defines:

  • _MSC_VER for Visual Studio (which incidentally also gives the version of the compiler, but we won’t use this information),
  • __GNUC__ for gcc,
  • __clang__ for clang.

You’ll note that they use the naming convention that C++ programmers are not allowed to use: two consecutive underscores, and a name starting with an underscore followed by a capital letter. The very reason why we can’t use them is because they are reserved to the compiler. Like here.

Note the else part in the above code. I think it is necessary to define the same macros as in the if and else if branches. Even if you don’t use another compiler than Visual Studio, gcc or clang today, it would be a shame to halt the compilation on another compiler just because you didn’t define the macros for it.

Or perhaps you don’t want your code to run on a compiler you don’t officially support. In any case, if this is what you want then a better option is to write somewhere else some specific macro-code to prevent the code from compile on non-supported compilers.

In summary, here is all the code put together:

#if defined(_MSC_VER)
    #define DISABLE_WARNING_PUSH           __pragma(warning( push ))
    #define DISABLE_WARNING_POP            __pragma(warning( pop )) 
    #define DISABLE_WARNING(warningNumber) __pragma(warning( disable : warningNumber ))

    #define DISABLE_WARNING_UNREFERENCED_FORMAL_PARAMETER    DISABLE_WARNING(4100)
    #define DISABLE_WARNING_UNREFERENCED_FUNCTION            DISABLE_WARNING(4505)
    // other warnings you want to deactivate...
    
#elif defined(__GNUC__) || defined(__clang__)
    #define DO_PRAGMA(X) _Pragma(#X)
    #define DISABLE_WARNING_PUSH           DO_PRAGMA(GCC diagnostic push)
    #define DISABLE_WARNING_POP            DO_PRAGMA(GCC diagnostic pop) 
    #define DISABLE_WARNING(warningName)   DO_PRAGMA(GCC diagnostic ignored #warningName)
    
    #define DISABLE_WARNING_UNREFERENCED_FORMAL_PARAMETER    DISABLE_WARNING(-Wunused-parameter)
    #define DISABLE_WARNING_UNREFERENCED_FUNCTION            DISABLE_WARNING(-Wunused-function)
   // other warnings you want to deactivate... 
    
#else
    #define DISABLE_WARNING_PUSH
    #define DISABLE_WARNING_POP
    #define DISABLE_WARNING_UNREFERENCED_FORMAL_PARAMETER
    #define DISABLE_WARNING_UNREFERENCED_FUNCTION
    // other warnings you want to deactivate... 

#endif

You can then use the macros this way:

DISABLE_WARNING_PUSH

DISABLE_WARNING_UNREFERENCED_FORMAL_PARAMETER
DISABLE_WARNING_UNREFERENCED_FUNCTION 

/*
code where you want 
to disable the warnings
*/

DISABLE_WARNING_POP

A great responsibility

This leads to code that is both concise and portable across compilers. Indeed, if you need to support a new compiler, you can just add a new branch to the #if defined statement.

But before you get into all this, heed the advice of Effective C++ and “Pay attention to compiler warnings.” Only once you did that, and if you know what you’re doing, use the above code to silence a warning in a portion of your code.

You will also like

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin