1

I have checked questions that are similar. This is close, but not a duplicate.

In essence I want to call a function on a parameter pack of base classes if present. I have a C++11 way of doing this that works, but it does not feel satisfactory to me.

Can someone offer a better [i.e. better performance and less boilerplate code]:

source code:

#include <iostream>
#include <type_traits>

using namespace std;

// a class initialised with an int that can't do it
struct A
{
    A(int a) : _a(a) {  }
    void report() const { std::cout << _a << std::endl; }
private:
    int _a;
};

// a class initialised with a string that can do it
struct B
{
    B(std::string s) : _b (move(s)) {  }
    void report() const { std::cout << _b << std::endl; }
    void do_it() { std::cout << "B did it with " << _b <<"!" << std::endl; }
private:
    string _b;
};

// a class initialised with an int that can do it
struct D
{
    D(int d) : _d(d) {  }
    void report() const { std::cout << _d << std::endl; }
    void do_it() { std::cout << "D did it with " << _d <<"!" << std::endl; }
private:
    int _d;
};

// a class initialised with a string that can't do it
struct E
{
    E(std::string s) : _e(move(s)) { }
    void report() const { std::cout << _e << std::endl; }
private:
    string _e;
};

// a function enabled only if T::do_it is a member function pointer
// the bool is there just to make this function more attractive to the compiler
// than the next one, below
template<class T>
auto do_it(T& t, bool)
-> typename std::enable_if<std::is_member_function_pointer<decltype(&T::do_it)>::value, void>::type
{
    t.do_it();
}

// a catch-all function called when do_it<T> is not valid
// the ... is less attractive to the compiler when do_it<T>(T&, bool) is available
template<class T>
void do_it(T& t, ...)
{
}

// a compound class derived from any number of classes - I am so lazy I work hard at
// being lazy.
template<class...Templates>
struct C : public Templates...
{
    // construct from a parameter pack of arbitrary arguments
    // constructing each base class with one argument from the pack
    template<class...Args>
    C(Args&&...args)
    : Templates(std::forward<Args>(args))...
    {

    }

    // private implementation of the dispatch mechanism here...
private:
    // this will call ::do_it<T>(T&, bool) if T::do_it is a member function of T, otherwise
    // calls ::do_it<T>(T&, ...)
    template<class T>
    void may_do_it()
    {
        ::do_it(static_cast<T&>(*this), true);
    }

    // calls may_do_it for the last class in the parameter pack
    template<typename T1>
    void multi_may_do_it()
    {
        may_do_it<T1>();
    }

    // general case for calling do_it on a parameter pack of base classes
    template<typename T1, typename T2, typename...Rest>
    void multi_may_do_it()
    {
        may_do_it<T1>();
        multi_may_do_it<T2, Rest...>();
    }

    // calls may_do_it for the last class in the parameter pack
    template<typename T1>
    void multi_report() const
    {
        static_cast<const T1&>(*this).report();
    }

    // general case for calling do_it on a parameter pack of base classes
    template<typename T1, typename T2, typename...Rest>
    void multi_report() const
    {
        static_cast<const T1&>(*this).report();
        multi_report<T2, Rest...>();
    }


    // the functions we actually wish to expose here...
public:
    // disptach T::do_it for each valid T in base class list
    void do_it() {
        multi_may_do_it<Templates...>();
    }

    // dispatch T::report, which must exist for each base class
    void report() const {
        cout << "-- all base classes reporting:" << endl;
        multi_report<Templates...>();
        cout << "-- all base classes reported" << endl;
    }
};

int main()
{
    C<A,B, D, E> c(10, "hello", 7, "goodbye");
    c.report(); // all base classes must report
    c.do_it(); // all base classes that can do_it, must.

   return 0;
}

output:

Compiling the source code....
$g++ -std=c++11 main.cpp -o demo -lm -pthread -lgmpxx -lgmp -lreadline 2>&1

Executing the program....
$demo 
-- all base classes reporting:
10
hello
7
goodbye
-- all base classes reported
B did it with hello!
D did it with 7!
Niall
  • 30,036
  • 10
  • 99
  • 142
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • It would be nice if you _boil_ your current post down to specific problem/code snippet – P0W Oct 01 '14 at 13:22
  • unhappily, template unpacking is always longwinded. This is the smallest, fully compilable program that illustrates what I want. – Richard Hodges Oct 01 '14 at 13:26
  • Thanks. That's awesome. This also allows accumulation of results in the case where do_it returns something (would need to be commuted to an optional of course). Is there a way to avoid the use of (...) on do_it<>? It feels wrong... – Richard Hodges Oct 01 '14 at 14:01
  • @RichardHodges Well, you can do `template auto do_it(T* t) -> /* SFINAE stuff */` and then `template void do_it(const void *)`, but I'm not sure it adds much. – T.C. Oct 01 '14 at 14:36

1 Answers1

4

I think this is about as boilerplate-free as you can make it.

// a function enabled only if T::do_it is a member function pointer
template<class T>
auto do_it(T* t)
-> typename std::enable_if<std::is_member_function_pointer<decltype(&T::do_it)>::value, void>::type
{
    t->do_it();
}

// a catch-all function called when do_it<T> is not valid
// the const void * is less attractive to the compiler when do_it<T>(T*) is available
template<class T>
void do_it(const void *)
{
}

// a compound class derived from any number of classes - I am so lazy I work hard at
// being lazy.
template<class...Templates>
struct C : public Templates...
{
    //constructor omitted
private:
    using expander = int[];
public:
    // disptach T::do_it for each valid T in base class list
    void do_it() {
        (void) expander{ 0, (::do_it<Templates>(this), 0)...};
    }

    // dispatch T::report, which must exist for each base class
    void report() const {
        cout << "-- all base classes reporting:" << endl;
        (void) expander{ 0, (Templates::report(), 0)...};
        cout << "-- all base classes reported" << endl;
    }
};

Demo.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • many thanks. I found that to eliminate warnings I have to name the expander and then #pragma the warnings away (clang). `expander _{ 0, (::do_it(*this, true), 0)...}; #pragma unused(_)` – Richard Hodges Oct 01 '14 at 14:14
  • `expander` may be `std::initializer_list`. – Jarod42 Oct 01 '14 at 14:15
  • 1
    @RichardHodges Just cast it to `void` - `(void) expander{/* stuff */};`. – T.C. Oct 01 '14 at 14:16
  • oooh! You got rid of the horrible `(...)`. Fantastic - thank you. It's now almost simple enough to put into application code... – Richard Hodges Oct 01 '14 at 16:32
  • @Jarod42 Thanks for the tip. This makes it very easy to enumerate lists of optional answers, eg: ` int value_sum() const { int total = 0; for(const auto& i : { value_of(this)... }) { total += i; } return total; } ` – Richard Hodges Oct 01 '14 at 17:08