CS 70

Avoid Warnings for Stubs

  • Dog speaking

    I need to make a stub for gcd because I'm not ready to write it yet.

  • Goat speaking

    Meh, easy peasy. I'll do it.

    int gcd(int x, int y) {
        // TODO: write the code
    }
    
  • Hedgehog speaking

    Ugh. I just tried that. I get tons of warnings, and it's a bit overwhelming.

  • Goat speaking

    Meh, I just ignore them, no big deal.

  • LHS Cow speaking

    Training yourself to ignore warnings generated by the compiler. Even as you're building your code, you should aim for no warnings ever.

  • RHS Cow speaking

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

  • Goat speaking

    Why isn't it just an error not to return a value when you're supposed to?

  • Cat speaking

    And why does main have a special rule?

  • Bjarne speaking

    C++ inherits this behavior from C, so it is part of our heritage.

  • Goat speaking

    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 returns at all.

Throwing Exceptions: std::logic_error

One way to suppress an error about not returning 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.

  • Goat speaking

    Meh, a lot of work just to crash our code, I've gotta include a header file and remember an exception name…?

  • LHS Cow speaking

    Well, honestly, it's not much work at all. But if you don't like it, there are other options.

  • Hedgehog speaking

    But if I'm happy with this I don't need to learn about other ways to do it?

  • LHS Cow speaking

    That's right.

  • Pig speaking

    But I always like to know MORE!!!

Other Options to Avoid Ever Returning

Throwing Exceptions: throwing Any Type

C++ allows any type at all to be thrown 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 throws 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 returning a value. The first two options are the best, because they make it easier for us to write tests for functions that are stubs.

  • Horse speaking

    Woah, woah! Our code still doesn't have a return statement. Why did the warnings stop?

  • LHS Cow speaking

    The compiler knows that both calling std::abort and throwing an exception leaves the function and never comes back.

  • Bjarne speaking

    Technically, std::abort is declared with a special [[noreturn]] attribute.

  • Duck speaking

    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.

  • Dog speaking

    I'm all set. Thanks!

  • Cat speaking

    This seems a bit hacky. Is there a better way?

  • LHS Cow speaking

    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.

  • Hedgehog speaking

    And I should get rid of [[maybe_unused]] when I write the function?

  • LHS Cow speaking

    Yes, and the code to throw an exception, too.

  • Pig speaking

    Are there MORE attributes?

  • Bjarne speaking

    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.

  • LHS Cow speaking

    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.

  • Bjarne speaking

    And compiler warnings are important information. We shouldn't ignore them.

  • LHS Cow speaking

    Exactly. Please follow these rules for writing stubs to avoid warnings while you're writing your code.

(When logged in, completion status appears here.)