kamping-site/kamping

Add possibility to add plugins

Hespian opened this issue · 1 comments

For example for additional features that go beyond kampings core functionality or for student-code that isn't up to kampings code quality or doesn't follow our coding style / some other reason why we don't want to be responsible for maintenance.

Here's a proposal that allows both adding functionality and replacing existing functionality with new one.

https://godbolt.org/z/WcM8vb1hG

Code
#include <iostream>

// kamping/communicator.hpp:
template<template<typename> typename... plugins>
class kampingComm: public plugins<kampingComm<plugins...>>... {
    public:
    void reduce() {std::cout << "Base Reduce" << std::endl;}
    int rank() {return 1;}
};

// better_reduce1.hpp: (plugin)
template<typename Comm>
class BetterReduceComm1 {
    public:
    void reduce() {std::cout << static_cast<Comm&>(*this).rank() << " Better Reduce1" << std::endl;}
    void addition1() {std::cout << static_cast<Comm&>(*this).rank() << " Better Addition1" << std::endl;}
};

// better_reduce2.hpp: (plugin)
template<typename Comm>
class BetterReduceComm2 {
    public:
    void reduce() {std::cout << static_cast<Comm&>(*this).rank() << " Better Reduce2" << std::endl;}
};

// my_code.cpp
struct MyComm : public kampingComm<BetterReduceComm1, BetterReduceComm2>{
    // Use reduce from BetterReduce2
    using BetterReduceComm2::reduce;
};

int main() {
    // Don't overwrite anything (only use additional functionality)
    kampingComm<BetterReduceComm2,BetterReduceComm1> comm;
    comm.reduce();
    comm.addition1();
    std::cout << comm.rank() << std::endl;

    std::cout << "-------------" << std::endl;

    // With overwritten reduce
    MyComm comm2;
    comm2.reduce();
    comm2.addition1();
    std::cout << comm2.rank() << std::endl;
}

Here's another example that shows how to implement plugins that depend on another plugin (which is slightly less pretty):

https://godbolt.org/z/zn9o16dP3

Code
#include <iostream>

// kamping/communicator.hpp:
template<template<typename> typename... plugins>
class kampingComm: public plugins<kampingComm<plugins...>>... {
    public:
    void reduce() {std::cout << "Base Reduce" << std::endl;}
    int rank() {return 1;}
};

// better_reduce1.hpp: (plugin)
template<typename Comm>
class BetterReduceComm1 {
    public:
    void reduce() {std::cout << static_cast<Comm&>(*this).rank() << " Better Reduce1" << std::endl;}
    void addition1() {std::cout << static_cast<Comm&>(*this).rank() << " Better Addition1 from BetterReduce1 plugin" << std::endl;}
};

// better_reduce2.hpp: (plugin)
template<typename Comm>
class BetterReduceComm2 {
    public:
    void reduce() {std::cout << static_cast<Comm&>(*this).rank() << " Better Reduce2" << std::endl;}
};

// better_addition1.hpp: (plugin that also has a function called addition1)
template <typename Comm>
class BetterAdditionComm1 {
    public:
    void addition1() {std::cout << static_cast<Comm&>(*this).rank() << " Better Addition1 from BetterAddition1 plugin" << std::endl;}
};

// advanced_reduce.hpp (plugin that depends on BetterReduce1)
// The user needs to also use the plugin BetterReduce1 if they want to use AdvancedReduce
template<typename Comm>
class AdvancedReduce {
    public:
    void reduce() {
        std::cout << "- Advanced reduce begin -" << std::endl;
        // explicitly call addition1 from BetterReduce1. Otherwise there might be an ambiguity if any other plugin adds a function called addition1
        // (which is the case in the example below)
        static_cast<Comm*>(this)->template BetterReduceComm1<Comm>::addition1();
        std::cout << static_cast<Comm&>(*this).rank() << " Advanced Reduce" << std::endl;
        std::cout << "- Advanced reduce end -" << std::endl;

    }
};


// my_code.cpp
struct MyComm : public kampingComm<BetterReduceComm1, BetterReduceComm2, BetterAdditionComm1, AdvancedReduce>{
    // Use reduce from AdvancedReduce
    using AdvancedReduce::reduce;
    // Use addition1 from BetterAddition1
    using BetterAdditionComm1::addition1;
};

// This is broken because you cannot use AdvancedReduce without BetterReduceComm1
struct BrokenComm : public kampingComm<AdvancedReduce>{
    // Use reduce from AdvancedReduce
    using AdvancedReduce::reduce;
};

int main() {
    // Don't overwrite anything (only use additional functionality)
    kampingComm<BetterReduceComm1, BetterReduceComm2, AdvancedReduce> comm;
    comm.reduce();
    comm.addition1();
    std::cout << comm.rank() << std::endl;

    std::cout << "-------------" << std::endl;

    // With overwritten reduce
    MyComm comm2;
    comm2.reduce();
    comm2.addition1();
    std::cout << comm2.rank() << std::endl;

    // This would not compile
    // BrokenComm comm3;
    // comm3.reduce();
}