Use a pseudo-CRTP to add modular support for something.
template<class T, std::size_t nrows, std::size_t ncols>
class Matrix;
template<class T, std::size_t size>
struct MatrixDiagonalSupport {
auto self() { return static_cast<Matrix<T, size, size>*>(this); }
auto self() const { return static_cast<Matrix<T, size, size> const*>(this); }
void setIdentity() {
for (std::size_t i = 0; i < size; ++i) {
for (std::size_t j = 0; j < i; ++j) {
(*self())(i,j) = {};
}
(*self())(i,i) = 1; // hope T supports this!
for (std::size_t j = i+1; j < size; ++j) {
(*self())(i,j) = {};
}
}
}
};
template<class T>
struct empty_t {};
template<bool b, class T>
using maybe= std::conditional_t<b, T, empty_t<T>>;
template<typename T, std::size_t nrows, std::size_t ncols>
class Matrix: public maybe<nrows==ncols,MatrixDiagonalSupport<T, nrows>>
{
// ...
Here we inherit from nothing if we aren't diagonal, and a class implementing set identity if we are diagonal.
Users of Matrix
get .setIdentity()
from its parent magically if it is right.
static_cast
inside self()
ends up being a zero-cost abstraction and giving the base class access to the child class.
This is pseudo-CRTP because we don't actually pass the derived class type to the parent, just enough information for the parent to reconstruct it.
This solution makes the method an actual method, and avoids any kind of SFINAE trickery.
Live example
In C++11 replace conditional_t<?>
with typename conditional<?>::type
:
template<bool b, class T>
using maybe=typename std::conditional<b, T, empty_t<T>>::type;
and everything should compile.