Avoid Warnings for Stubs
I need to make a stub for
gcd
because I'm not ready to write it yet.Meh, easy peasy. I'll do it.
int gcd(int x, int y) { // TODO: write the code }
Ugh. I just tried that. I get tons of warnings, and it's a bit overwhelming.
Meh, I just ignore them, no big deal.
Training yourself to ignore warnings generated by the compiler. Even as you're building your code, you should aim for no warnings ever.
Let's dive into the details of what's going on here, why you get warnings, and how to avoid them…
If you write a stub like this one,
/*
* greatest common divisor
* \param x a positive integer
* \param y a positive integer
* \returns largest number that divides both x and y
*/
int gcd(int x, int y) {
// TODO: write the code
}
clang++
will produce the following warnings:
gcd.cpp:7:13: warning: unused parameter 'x' [-Wunused-parameter]
int gcd(int x, int y) {
^
gcd.cpp:7:20: warning: unused parameter 'y' [-Wunused-parameter]
int gcd(int x, int y) {
^
gcd.cpp:9:1: warning: non-void function does not return a value [-Wreturn-type]
}
^
3 warnings generated.
The “unused parameter” warnings are the result of our not using the parameters we specified in the body of the function (yet, obviously). The “non-void function does not return a value” is caused by the lack of a return
statement that returns an int
.
When we have a function that's supposed to return a value—a “non-void” function—reaching the closing brace and exiting the function without actually returning a value is undefined behavior. As a result, calling one of our stubs could (in principle) cause anything to happen when it fails to return a value when it should (including an “illegal hardware instruction” error).
(FWIW, it's actually okay for main
to omit the return
statement, because it implicitly returns zero if we reach its closing curly brace. But it's still good style to explicitly add return 0
anyway, rather than relying on other people knowing this one main
-specific oddity.)
Why isn't it just an error not to return a value when you're supposed to?
And why does
main
have a special rule?C++ inherits this behavior from C, so it is part of our heritage.
So we're living like it's 1972! Meh.
Avoiding Warnings
Usually, a warning about a function not returning a value when it is supposed to is hugely important and indicates a major problem with our code (one that really ought to be an error), so we should not get in the habit of ignoring these warnings.
Happily, there are several ways of avoiding these common warnings about stub functions.
Preventing the “Does Not Return a Value” Warning
For our stubs, we can suppress these warnings by making sure the function never return
s at all.
Throwing Exceptions: std::logic_error
One way to suppress an error about not return
ing a proper value is to make our code throw
an exception:
#include <stdexcept>
/*
* greatest common divisor
* \param x a positive integer
* \param y a positive integer
* \returns largest number that divides both x and y
*/
int gcd(int x, int y) {
throw std::logic_error("Not implemented (yet!)"); // FIXME: Implement it!
}
std::logic_error
is a C++–standard-library class designed to represent logical errors in the program. With this code, we not only get rid of the compiler warning, but we also have well-defined behavior if we call our stub.
The CS 70 test suite catches exceptions, and if we had a test for gcd
, it would fail as
!! Unexpected exception: std::logic_error -- Not implemented (yet!)
FAILURE (after 0 passes): test-gcd.cpp:7: gcd(21,35), expecting 7
To use std::logic_error
, we must add #include <stdexcept>
to our code.
Meh, a lot of work just to crash our code, I've gotta include a header file and remember an exception name…?
Well, honestly, it's not much work at all. But if you don't like it, there are other options.
But if I'm happy with this I don't need to learn about other ways to do it?
That's right.
But I always like to know MORE!!!
Other Options to Avoid Ever Returning
Throwing Exceptions: throw
ing Any Type
C++ allows any type at all to be throw
n as an exception, so we could just write our function as
/*
* greatest common divisor
* \param x a positive integer
* \param y a positive integer
* \returns largest number that divides both x and y
*/
int gcd(int x, int y) {
throw "Not implemented (yet!)"; // FIXME: Implement it!
}
This form of the code throw
s a plain C-style string as its result. Although it's arguably less idiomatic than using standard-library exceptions, it does also work with the CS 70 test suite, producing
!! Unexpected exception: char const* -- Not implemented (yet!)
FAILURE (after 0 passes): test-gcd.cpp:7: gcd(21,35), expecting 7
Use std::abort
If for some reason we wanted to avoid exceptions, we could call std::abort
, to abort the program:
#include <cstdlib>
/*
* greatest common divisor
* \param x a positive integer
* \param y a positive integer
* \returns largest number that divides both x and y
*/
int gcd(int x, int y) {
std::abort(); // FIXME: Implement it!
}
All of these options work prevent the compiler from warning us about the gcd
function not return
ing a value. The first two options are the best, because they make it easier for us to write tests for functions that are stubs.
Woah, woah! Our code still doesn't have a
return
statement. Why did the warnings stop?The compiler knows that both calling
std::abort
andthrow
ing an exception leaves the function and never comes back.Technically,
std::abort
is declared with a special[[noreturn]]
attribute.Don't forget that we still have the
unused-parameter
warnings to deal with.
Preventing “Unused Parameter” Warnings
Again, creating a variable and then not using it is usually a sign of a problem with our code, but when we're writing a function stub, we know the argument isn't used (yet), so these warnings just make it harder to see warnings that might indicate actual problems in our code.
The Easy Way, Disable the Warning
What we often rcommend as a quick and easy option is to just temporarily disable this warning. We can do this by adding the following lines to the top of our implementation file while it's unfinished:
#pragma GCC diagnostic ignored "-Wunused-private-field" // FIXME: Remove soon.
#pragma GCC diagnostic ignored "-Wunused-parameter" // FIXME: Remove soon.
We just have to remember to remove these lines once we've implemented our functions as they were just a stop-gap measure. If you leave them in when you submit your code, you'll won't get full credit as you forgot a FIXME.
FWIW, if you want to be super clean about it, you can also make it so that only the gcd
function is allowed to have unused parameters:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter" // FIXME: Remove soon.
int gcd(int x, int y) {
throw std::logic_error("Not implemented (yet!)"); // FIXME: Implement it!
}
#pragma GCC diagnostic pop
The push
and pop
operations save the current state of the compiler warnings, so that we can turn off warnings for just the gcd
function, and then restore the previous state of the warnings.
I'm all set. Thanks!
This seems a bit hacky. Is there a better way?
Yes, there is. At least for some definitions of “better”.
The problem with this approach is that it's compiler specific. It works on GCC-compatible compilers, which include the GNU Compilers, the Clang compiler, and the Intel compiler, but not the Microsoft Visual C++ compiler.
So while this is a quick and easy (and instructor-approved) approach, it's not the best approach for writing code that will be used in the real world, so it's good to know “the right way” to do it.
Don't Name Variables
One option for avoiding the “unused parameter” warning is to not name our variables, but instead just give their types, as in
#include <stdexcept>
/*
* greatest common divisor
* \param x a positive integer
* \param y a positive integer
* \returns largest number that divides both x and y
*/
int gcd(int, int) {
throw std::logic_error("Not implemented (yet!)"); // FIXME: Implement it!
}
This option is easy, but it's a bit annoying because we'll still have to put the names in when we fill out the stub.
Mark Variables as Unused
A better alternative to not naming variables is to mark them (for now) as not being used:
#include <stdexcept>
/*
* greatest common divisor
* \param x a positive integer
* \param y a positive integer
* \returns largest number that divides both x and y
*/
int gcd([[maybe_unused]] int x, [[maybe_unused]] int y) {
throw std::logic_error("Not implemented (yet!)"); // FIXME: Implement it!
}
The [[maybe_unused]]
part in front of the variable declaration is a C++ attribute specifier, telling the compiler that our not using this parameter is okay.
And I should get rid of
[[maybe_unused]]
when I write the function?Yes, and the code to throw an exception, too.
Are there MORE attributes?
Several. I already mentioned that function can be marked
[[noreturn]]
so it won't return. We can also mark functions as[[deprecated]]
or[[deprecated("reason")]]
to highlight an old function that shouldn't be used anymore, and[[nodiscard]]
to say that the function returns something that really shouldn't be casually thrown away.Thanks. You can read more about attributes on-line. Some compilers have their own compiler-specific attributes, too.
By making the body of the function throw
an exception and marking parameters as [[maybe_unused]]
, we can avoid warnings about our stubs, and thus make it easier to see warnings that matter.
Summary
There are multiple ways to write stubs and not have warnings. Pick one you like and use it.
And compiler warnings are important information. We shouldn't ignore them.
Exactly. Please follow these rules for writing stubs to avoid warnings while you're writing your code.
(When logged in, completion status appears here.)