Monday, 8 October 2018

Warnings Series - Hidden Overloads

In previous posts I've described how to build Clang on RHEL. This allows us to have access to an alternative compiler to help catch errors early. In this and some upcoming blog posts I'm going to go over some of the errors that using multiple compilers has helped me catch.

Hidden Overloads

Below is some example code which has multiple types of Dice. The base type is a standard 6 sided Dice and it is using inheritance to make a 20 sided Dice.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// library code
struct Dice {
    virtual ~Dice() = default;
    // Do a "random" roll of the dice
    virtual int roll() { return 6; }
    virtual int min() { return 1; }
    virtual int max() { return 6; }
};
 
struct TwentySidedDice : public Dice {
    virtual ~TwentySidedDice() = default;
    // Do multiple random rolls of the dice
    virtual int roll(int times) { return times * 3; }
    virtual int max() { return 20; }
};

As with all good code, we have some unit tests to make sure everything is working:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// unit tests
void test_dice() {
    Dice die;
    assert(die.roll() == 6);
    assert(die.min() == 1);
    assert(die.max() == 6);
}
 
void test_twenty_sided() {
    TwentySidedDice die;
    assert(die.roll(4) == 12);
    assert(die.min() == 1);
    assert(die.max() == 20);
}

The above is compiled without any warnings on GCC (using -Wall) and as all unit tests pass, we feel like we can ship the code as a library.

Problem with the code

If a client attempted to use the above code, they might try something like this:

1
2
3
4
5
6
7
8
9
// client code
int main()
{
    auto die = std::make_unique<TwentySidedDice>();
    std::cout << "min is " << die->min() << std::endl;
    std::cout << "max is " << die->max() << std::endl;
    std::cout << "roll is " << die->roll() << std::endl;
    return 0;
}

On first look this code seems reasonable and looks like it should work. However, you would see the following error:

1
2
3
4
5
6
7
8
9
<source>: In function 'int main()':
<source>:43:42: error: no matching function for call to 'TwentySidedDice::roll()'
     std::cout << "roll is " << die->roll() << std::endl; // no matching function error
                                          ^
<source>:16:17: note: candidate: 'virtual int TwentySidedDice::roll(int)'
     virtual int roll(int times) { return times * 3; }
                 ^~~~
<source>:16:17: note:   candidate expects 1 argument, 0 provided
Compiler returned: 1

The reason for the error is that based on the rules of C++ overload resolution the int roll(int i); function in TwentySidedDice is hiding the int roll(); function from Dice.

Catching the error

Clang

If you compile the library code with clang (again with -Wall) it gets the following warning:

1
2
3
4
5
6
7
8
<source>:16:17: warning: 'TwentySidedDice::roll' hides overloaded virtual function [-Woverloaded-virtual]
    virtual int roll(int times) { return times * 3; }
                ^
<source>:9:17: note: hidden overloaded virtual function 'Dice::roll' declared here: different number of parameters (0 vs 1)
    virtual int roll() { return 6; }
                ^
1 warning generated.
Compiler returned: 0

This shows the error early and would let a developer know about the potential problem, so that it can be resolved before shipping to users.

GCC

As mentioned, on GCC with the compiler flags -Wall, there are no errors in the code. To enable this warning on GCC you can explicitly add the -Woverloaded-virtual flag.

MSVC

On MSVC compiling with the /W4 flag, doesn't show any warnings as the C4264 warning is off by default. To see the error you can enable warning C4264 or you can see the following using `/Wall:

1
2
3
4
<source>(16): warning C4263: 'int TwentySidedDice::roll(int)': member function does not override any base class virtual member function
<source>(18): warning C4264: 'int Dice::roll(void)': no override available for virtual member function from base 'Dice'; function is hidden
<source>(9): note: see declaration of 'Dice::roll'
<source>(7): note: see declaration of 'Dice'

Comparing the errors

To view the errors from each compiler you can use godbolt to compare the output

No comments:

Post a Comment