I have a template class in c++ that depends on a few nontype template parameters. The implementation of some methods, and some field member, depend on the value of the template parameters, in a nontrivial way. This essentially require partial template specialization of the methods, which (being not directly doable in c++) is implemented with a series of if-constexpr
constructs.
I am looking for a refactoring of the code in a sensible and mantainable way.
I will try to illustrate the problem by making some simplifications. The class looks like that
constexpr property_A(unsigned int prop);
constexpr property_B(unsigned int prop);
template <unsigned int N, unsigned int prop, unsigned int type>
class Model {
// Several common methods and fields (depending on N)
auto method_common();
some_type m_common;
// This depends on prop and type
using type_field = ...
type_field m_field;
auto private_method() { // depends on prop and type }
public:
auto some_public_method_typeA() { // depends on property_A(prop), calls private_method() }
auto some_public_method_typeB() { //depends on property_B(prop), calls private_method() }
};
};
The template parameters prop
and type
are actually allowed to take a finite range of values.
The two constexpr property_A
and property_B
conveniently map the template parameter prop
to a value that defines the specialziation of methods some_public_method_tyepA
and some_public_method_typeB
.
These 2 "type" of specializations are fundamentally different, something like:
property_A(0) = 0
property_A(1) = property_A(3) = 1;
property_A(2) = property_A(4) = 2;
property_B(0) = 0;
property_B(1) = property_B(2) = 1;
property_B(3) = property_B(4) = 2;
Additionally, there are methods that depends directly on prop
(i.e., not through property_A(prop)
or property_B(prop)
).
The code is at the moment implemented for type=0
only, and the various partial specializations are realized with a std::conditional
(or similar) and if-constexpr
:
using_type_field = std::conditional_t<(property_A(prop) == 0), type1, type2>; // or more complex choice
some_public_method_type_A {
if constexpr (property_A(prop) == 0) {
// specialized code
}
else if constexpr (property_A(prop) == 1) {
// specialized code
}
}
some_public_method_type_B {
if constexpr (property_B(prop) == 0) {
// specialized code
}
else if constexpr (property_B(prop) == 1) {
// specialized code
}
}
I am going to implement the code for type=1
. Since this "specialization" could require other methods, and also to keep some flexibility, I am inclined to use CRTP to "specialize" to type==1
, while retaining the common interface:
constexpr property_A(unsigned int prop);
constexpr property_B(unsigned int prop);
template <unsigned int N, unsigned int prop, class Derived>
class Model_Base {
// Several common methods and fields (depending on N)
auto method_common();
some_type m_common;
auto private_method() { return static_cast<Derived *>(this)->private_method(); }
public:
some_public_method_type_A {
if constexpr (property_A(prop) == 0) {
// specialized code
// Calls private_method, implemented in Derived
private_method();
}
some_public_method_type_B {
if constexpr (property_B(prop) == 0) {
// specialized code
}
else if constexpr (property_B(prop) == 1) {
// specialized code
// Calls private_method, implemented in Derived
private_method();
}
}
};
template <unsigned int N, unsigned int prop>
class Model_type0 : Model<N, prop, Model_type0>
friend class Model<N, prop, Model_type0>; // To give access to private_method
// Depends on prop
using type_field = ...
type_field m_field;
auto private_method();
};
template <unsigned int N, unsigned int prop>
class Model_type1 : Model<N, prop, Model_type0>
friend class Model<N, prop, Model_type1>; // To give access to private_method
// Depends on prop, possibly different than Model_type0
using type_field = ...
type_field m_field;
auto private_method();
auto some_other_method();
};
// For convenience, can be more systematically defined if there are many possible values of template parameter type
template <unsigned int N, unsigned int prop, unsigned int type>
using Model = std::conditional_t<(type == 0), Model_type0<N, prop>, Model_type1<N, prop>>;
While this plan seems ok, I am looking for advice for a possibly better design, in terms of maintainability and flexibility. In particular, if I ever add another possible value of prop
template parameter, I will need to add yet-another if-constexpr
branch in the various public methods, rendering the code more complex...
Performance is important, so no virtual methods
.
I use c++17
, but I am open to use c++20
if this brings decisive advantages.
template unsigned int
relevant only for those values of prop; to do that, I would probably think to replace prop by a simple struct that holds this second parameter, when relevant.property_A
independant ofproperty_B
, so you could carve them out into separate classes, and haveModel
just compose those?