C++: [[maybe_unused]

When you declare a function or a method with an argument that will not be used, the compiler emits a warning (if -Wunused-parameter is enabled in gcc and clang) about that issue.

See the code below:

#include <iostream>
#include <string>

void print_msg(const std::string& e)
{
  std::cout << "Hello world\n";
}

int main()
{
  print_msg("Friday");
  return 0;
}

The g++ compiler output is;

In function 'void print_msg(const std::string&)':
<source>:4:35: warning: unused parameter 'e' [-Wunused-parameter]
    4 | void print_msg(const std::string& e)
      |                ~~~~~~~~~~~~~~~~~~~^

It is a good idea to enable this compiler flag (-Wunused-parameter) to remove all unused parameters from your code, making it cleaner.

That could mean removing the parameter completely like below:

void print_msg();

or simply removing the variable name from the argument list:

void print_msg(const std::string& );

Why would you choose the latter instead of the former?

  1. You are sure you will use that parameter later, so you prefer keeping it to avoid modifying all the places where your function is called.
  2. You are overriding a method where the parameter is not used and you cannot modify the interface of the virtual method you are implementing.
  3. You want to catch an exception but do not want to do anything with the exception itself (probably you want to ignore the exception, log a message, or generate a more generic error code).

However, in some scenarios, you simply do not know if a variable will be used or not (e.g. conditional compilation based on #define and #ifdef preprocessor directives or template-based code).

Consider this example:

You have a log subsystem but want to enable it with the LOG_ENABLED preprocessor definition. This is its implementation:

void log_msg(const std::string& msg)
{
#ifdef LOG_ENABLED    
    std::cout << msg << "\n";
#else
    // do nothing
#endif
}

int main()
{
  log_msg("This is a something to log");
}

If the LOG_ENABLED definition is set, everything will work as expected, otherwise the “unused parameter” warning will occur.

Since it is a good idea to enable such warnings AND it is also a good idea to have your code as clean and expressive as possible, [[maybe_unused]] is the hero here.

[[maybe_unused]] is an attribute introduced in C++17 that is used to mark structs, classes, variables, arguments, enums, etc. as “probably unused”, so the compiler simply ignores if such symbols are not being used in the scope they are defined. Thus, apart from removing the warning, marking a variable as “maybe unused” is a nice way to self-document the code and the intentions behind the symbols you create.

Your program will look like this with this attribute:

#include <iostream>
#include <string>


void log_msg([[maybe_unused]] const std::string& msg)
{
#ifdef LOG_ENABLED    
    std::cout << msg << "\n";
#else
    // do nothing
#endif
}

int main()
{
  log_msg("This is a something to log");
}

More on this attribute here: https://en.cppreference.com/w/cpp/language/attributes/maybe_unused

C++14: [[deprecated]]

[[deprecated]] is another attribute that is useful to mark something (a function, a method, a variable, a class, etc.) as still valid, but that has been superseded by other newer stuff and that probably will be removed in the future.

In a similar vein to [[nodiscard]], [[deprecated]] can return a message explaining why this entity has been marked as such.

The compiler will show a warning when a deprecated entity is being actually used in our code.

For example, I have this code:

#include <iostream>

void print(const std::string& msg)
{
    std::cout << msg << "\n";
}

int main()
{
    print("Hello world");
}

After that a lot of functions and code started to use my print() function, I realize that a newer version with std::string_view instead of std::string could have better performance and, since I do not want to break any code, I consider having both functions in my system.

So, to discourage the usage of my old function, I mark it as deprecated:

#include <iostream>


void println(std::string_view msg)
{
    std::cout << msg << "\n";
}

[[deprecated("Use println instead")]]
void print(const std::string& msg)
{
    std::cout << msg << "\n";
}

int main()
{
    print("Hello world");
}

But, since I am still using the old version in my main() function, the compiler will return a warning like this one:

main.cpp:17:24: warning: ‘void print(const string&)’ is deprecated: Use println instead [-Wdeprecated-declarations]

That will dissappear when I will replace all the invocation to print() with println().

C++17: [[nodiscard]] attribute

C++17 adds a new attribute called [[nodiscard]] to let the user know that a return value from a function or method should be handled properly or assigned to a value.

For example, look to this code:

int sum(int a, int b)
{
  return a + b;
}

int main()
{
  sum(10, 20);
  return 0;
}

It produces no result or side-effects, but if the programmer forgot assigning the return value to a variable by mistake, the error will not be immediately obvious.

Now, in this scenario:

char* getNewMessage()
{
  char* nm = new char[100];
  strcpy(nm, "Hello world");
  return nm;
}

int main()
{
  getNewMessage();
  return 0;
}

There is a memory leak produced because the returned value was not stored anywhere and there is no way to deallocate its memory.

Marking a function or method with [[nodiscard]], encourages the compiler to show a compilation warning when it is invoked and its return value is simply bypassed.

You can also write an additional message with the [[nodiscard]] attribute. That message will be displayed if a warning is generated.

In my examples, we could mark my functions like this:

#include <cstring>

[[nodiscard]]
int sum(int a, int b)
{
  return a + b;
}

[[nodiscard("Release the memory using delete[]")]]
char* getNewMessage()
{
  char* nm = new char[100];
  strcpy(nm, "Hello world");
  return nm;
}

int main()
{
  sum(10, 20);
  getNewMessage();
  return 0;
}

And in this case, g++ returns the following compilation warnings:

In function 'int main()':
<source>:19:6: warning: ignoring return value of 'int sum(int, int)', declared with attribute 'nodiscard' [-Wunused-result]

<source>:20:16: warning: ignoring return value of 'char* getNewMessage()', declared with attribute 'nodiscard': 'Release the memory using delete[]' [-Wunused-result]

Though using it could add a lot of verbosity to your method declarations, it is a good idea using it because it prevents some errors to occur.

More on [[nodiscard]]: https://en.cppreference.com/w/cpp/language/attributes