Search code examples
c++clang-tidydefault-arguments

Default arguments on virtual methods


I'm using Clang-Tidy to inspect my code. One of the tool's messages puzzled me:

clang-tidy: Default arguments on virtual or override methods are prohibited

I get the idea when I actually use a method override. But here in my case, I want to finalize the use of the method. So I put the "final" keyword at the end of my method signature to prevent it from overriding. And to do this, we must use "virtual" keyword as well.

I don't understand why I could't use default arguments there. If I remove the "virtual/final" attributes, Clang-Tidy leave me alone, but permits to shadow the method by accident, and I don't want that.

So, is this a false-positive ? A bug ? Or a real problem I should take care of ?

Here is the minimum reproducible example :

The case I want to avoid :

#include <iostream>

class A
{
    public:

        A () = default;
        virtual ~A () = default;

        virtual void overrideableMethod () {
            std::cout << "Method from A !" << std::endl;
        };

        void nonOverrideableMethodEvenByAccident (int a = 0) {
            std::cout << "Method from A ! V:" << a << std::endl;
        }
};

class B : public A
{
    public:

        B () : A() {}

        void overrideableMethod () override { // --> Fine.
            std::cout << "Method from B !" << std::endl;
        };

        void nonOverrideableMethodEvenByAccident (int a) { // -> Must be an error. (Omitting override here do the trick, so it's weak !)
            std::cout << "Method from B ! V:" << a << std::endl;
        };
};

int main(int argc, char const *argv[])
{
    B b{};
    b.overrideableMethod();
    b.nonOverrideableMethodEvenByAccident(9);

    /* output : 
Method from B !
Method from B ! V:9
    */

    return 0;
}

The case I want (A compiler error), but without the clang-tidy warning :

#include <iostream>

class A
{
    public:

        A () = default;
        virtual ~A () = default;

        virtual void overrideableMethod () {
            std::cout << "Method from A !" << std::endl;
        };

        virtual void nonOverrideableMethodEvenByAccident (int a = 0) final { // [WARNING] clang-tidy: Default arguments on virtual or override methods are prohibited
            std::cout << "Method from A ! V:" << a << std::endl;
        }
};

class B : public A
{
    public:

        B () : A() {}

        void overrideableMethod () override { // --> Fine.
            std::cout << "Method from B !" << std::endl;
        };

        void nonOverrideableMethodEvenByAccident (int a) { // [ERROR] error: virtual function ‘virtual void B::nonOverrideableMethodEvenByAccident(int)’ overriding final function (with or without override, great !)
            std::cout << "Method from B ! V:" << a << std::endl;
        };
};

int main(int argc, char const *argv[])
{
    B b{};
    b.overrideableMethod();
    b.nonOverrideableMethodEvenByAccident(9);

    return 0;
}

Solution

  • I don't understand why I could't use default arguments there.

    Well, you can, but it is against the Google C++ Style Guide. The reasoning is explained in the documentation, see Default Arguments.

    If you don't care about this rule, you can disable the google-default-arguments check (using --checks=-google-default-arguments).

    Another solution is to use an overloaded function without arguments instead of a function with default arguments. This does not result in the mentioned Clang-Tidy warning, but still does result in a compiler error when (accidentally) overriding nonOverrideableMethodEvenByAccident(), as you want:

    #include <iostream>
    
    class A
    {
        public:
    
            A () = default;
            virtual ~A () = default;
    
            virtual void overrideableMethod () {
                std::cout << "Method from A !" << std::endl;
            };
    
            virtual void nonOverrideableMethodEvenByAccident (int a) final { // No default argument!
                std::cout << "Method from A ! V:" << a << std::endl;
            }
    
            virtual void nonOverrideableMethodEvenByAccident (void) final { // Function without arguments
                nonOverrideableMethodEvenByAccident(0);
            }
    };
    
    class B : public A
    {
        public:
    
            B () : A() {}
    
            void overrideableMethod () override {
                std::cout << "Method from B !" << std::endl;
            };
    
    //      void nonOverrideableMethodEvenByAccident (int a) { // error: virtual function ‘virtual void B::nonOverrideableMethodEvenByAccident(int)’ overriding final function
    //          std::cout << "Method from B ! V:" << a << std::endl;
    //      };
    };
    
    int main(int argc, char const *argv[])
    {
        B b{};
        b.overrideableMethod();
        b.nonOverrideableMethodEvenByAccident(9);
        b.nonOverrideableMethodEvenByAccident(0);
    
        return 0;
    }
    

    Output:

    Method from B !
    Method from A ! V:9
    Method from A ! V:0