prestokeys
prestokeys

Reputation: 4849

Trick for overloads

Consider this code:

#include <iostream>

class A {
public:
    template <typename... Args> void execute(Args&&... args) {foo(std::forward<Args>(args)...);}
    template <typename... Args> void doIt(Args&&... args) {bar(std::forward<Args>(args)...);}
private:
    void foo() {}
    void foo(int) {}
    void foo(int, char) {}
    void foo(bool, int, double) {}
    void bar() {foo();}  // *** Added
    void bar(int) {}
    void bar(int num, char c) {foo(num, c);}  // *** Added
    void bar(bool, int, double) {}
};

int main() {
    A a;
    a.doIt();
    a.doIt(5,'t');
}

A::doIt is to have the same overloading types as A::execute and will use bar's overloads for some of them (for which bar's overloads are unique) and use foo's overloads for the others. To get this done, I simply added void bar() {foo();} and void bar(int num, char c) {foo(num, c);}. This is not that painful, but suppose there were many such forwarding to do. And think of the maintenance issues when new overloads of doIt are needed, and new foo overloads defined for them (easy to forget the forwarding stuff).

Is there a way to remove these extra bar overloads (that simply forward to foo), and instead modify the currently defined doIt(Args&&... args) function so that in case no such bar overload exists it will call up foo's overloads instead? So in other words, remove the two bar overloads that I added, and still have main() compile as intended due to doIt's new definition.

Upvotes: 0

Views: 105

Answers (2)

prestokeys
prestokeys

Reputation: 4849

Thanks to nwp's hint (though I'm not sure if this is what he meant), and T.C.'s debugging hint, I have this alternate solution. But T.C.'s solution is way better.

#include <iostream>

class A_Foo {
protected:
    virtual void foo() {std::cout << "foo().\n";}
    virtual void foo(int) {std::cout << "foo(int).\n";}
    virtual void foo(int, char) {std::cout << "foo(int, char).\n";}
    virtual void foo(bool, int, double) {std::cout << "foo(bool, int, double).\n";}
};

class A_Bar : private A_Foo {
public:
    using A_Foo::foo;  // This is needed.  Else all the declarations of foo in A_Foo are hidden.
    virtual void foo(int) override {std::cout << "bar(int).\n";}
    virtual void foo(bool, int, double) override {std::cout << "bar(bool, int, double).\n";}
};

class A : private A_Bar {
public:
    template <typename... Args> void execute(Args&&... args) {
        A_Foo::foo(std::forward<Args>(args)...);
    }
    template <typename... Args> void doIt(Args&&... args) {
        A_Bar::foo(std::forward<Args>(args)...);
    }
};

int main() {
    A a;
    a.doIt();  // foo().
    a.doIt(5);  // bar(int).
    a.doIt(5,'t');  // foo(int, char).
    a.doIt(true,5,1.8);  // bar(bool, int, double).
}

Upvotes: 0

T.C.
T.C.

Reputation: 137301

SFINAE on the well-formedness of bar(std::forward<Args>(args)...).

class A {
private:
    void foo();
    void foo(int);
    void foo(int, char);
    void foo(bool, int, double);
    void bar(int);
    void bar(bool, int, double);

    template <typename... Args> 
    auto doIt_impl(int, Args&&... args) -> decltype(bar(std::forward<Args>(args)...)){
        bar(std::forward<Args>(args)...);
    }
    template <typename... Args> 
    auto doIt_impl(long, Args&&... args) -> void {
        foo(std::forward<Args>(args)...);
    }

public:
    template <typename... Args> void doIt(Args&&... args) {
        doIt_impl(0, std::forward<Args>(args)...);
    }
};

The dummy first parameter ensures that the bar-calling overload of doIt_impl is preferred if viable. Also, it's important to note that trailing return types do not get class-scope lookup, so the declaration of doIt_impl must be after the declaration of the bars.

Demo.

Upvotes: 4

Related Questions