Add possibility to add plugins
Hespian opened this issue · 1 comments
Hespian commented
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.
Hespian commented
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();
}