Design : Policy based¶
The idea is to replace a specific usage patern of preprocessors.
Imagine you have a black box function do_smth
, this function may have multiple implementation do_smth_int1
, do_smth_int2
where one or the other are more suitable to specific architectures. Usually in c
it would be implemented like this.
void do_smth( args ...){
#ifdef _ARCHI1
do_smth_int1(args...)
#else
do_smth_int2(args...)
#endif
}
Such patern is not harmfull in itself but may be hard to read/debug for more complex usages.
moving to policy design¶
The idea would be to setup something similar as "replacing if defs", but in a more general way to be able to pass more complex objects and to avoid exposing the name globally (if you use a lot of autocompletion you will like this :) )
// somewhere in the code
#ifdef DOSTUFF
constexpr bool do_stuff_defined = true;
#else
constexpr bool do_stuff_defined = false;
#endif
void func(){
if constexpr (do_stuff_defined){
do_stuff();
}else{
dont_do_stuff();
}
}
The key part in such design is to have very abstract implementations :
Instead of this :
void do_stuff_arch1();
void do_stuff_arch2();
void do_stuff_arch3();
void do_stuff_arch4();
you want to have function like this :
template<u32 arch_code>
void do_stuff();
then the original exemple may be rewritten as such :
enum ArchCodes{
ARCHI_1, ARCHI_UNKNOWN
};
#ifdef _ARCHI_1
constexpr ArchCodes arch_code = ARCHI_1;
#else
constexpr ArchCodes arch_code = ARCHI_UNKNOWN;
#endif
void do_smth( args ...){
do_smth_int<arch_code>(args...);
}
A better exemple¶
Imagine you are coding a GPU kernel but some parameters may have to be tweaked to squeeze the best performance out of the card. The policy design might be very revelant.
The global definition is :
enum ArchCodes{
ARCHI_1, ARCHI_UNKNOWN
};
#ifdef _ARCHI_1
constexpr ArchCodes arch_code = ARCHI_1;
#else
constexpr ArchCodes arch_code = ARCHI_UNKNOWN;
#endif
When implementing your function you can do something like this.
struct SMTHPolicy{
enum ExecutionParams{
WorkerCount = (arch_code == ARCHI_1) ? 16 : 8;
};
using Operator = std::plus<u32>;
}
or equivalently :
struct SMTHPolicy{
static constexpr u32 WorkerCount = (arch_code == ARCHI_1) ? 16 : 8;
using Operator = std::plus<u32>;
}
and then :
void do_smth( args ...){
do_smth_int<SMTHPolicy>(args...);
}
The advantage of the using the enum
case is that the type of WorkerCount
is not specified and therefor can be better optimized