3
\$\begingroup\$

In simulating physical equations (such as fluid dynamics) it is common to write an application that deals with 1, 2, and 3 dimensions. A common C/Fortran approach is to make all vectors (for example) have a size of 3, and then to carry around a global constant to say how many elements are valid.

We have tried to use C++11 templating to serve the same purpose and the majority of our application is now templated by int DIM.

There are times that the difference between API for different dimensions requires entirely new classes. For this, we have come up with the following selector pattern -- it seems to be working well, but I was wondering if we have reinvented the wheel and missed something along the way?

In order to construct the pattern, we need several files:

ClassName_fwd.h:

This allows the user to reference the specific implementation of the dimension specific classes in a way that is consistent for all dimensions.

// forward decs
class ClassName_1;
class ClassName_2;
class ClassName_3;
template<int DIM>
struct ClassNameSelector { };
// 1D
template<>
struct ClassNameSelector<1> {
using ClassName = ClassName_1;
};
// 2D
template<>
struct ClassNameSelector<2> {
using ClassName = ClassName_2;
};
// 3D
template<>
struct ClassNameSelector<3> {
using ClassName = ClassName_3;
};
// typedef
template<int DIM>
using ClassName = typename ClassNameSelector<DIM>::ClassName;

ClassName.h

#include <path/to/ClassName_fwd.h>
#include <path/to/ClassName_1.h>
#include <path/to/ClassName_2.h>
#include <path/to/ClassName_3.h>

ClassName.cpp

// explicit instantiations
template struct ClassNameSelector<1>;
template struct ClassNameSelector<2>;
template struct ClassNameSelector<3>;

ClassName_N.h/cpp [optional]

Holds all of the functionality that can be implemented without care for dimension, in addition to pure virtual methods that should be overridden by the dimension-specific implementations. This class sets the dimensionless API for ClassName. In the cpp file, there should be explicit instantiations of this class. This class might be excluded to avoid pure virtual function calls.

/**
* All of the functions that have a constant implementation
* for all dimensions
*/
template<int DIM>
class ClassName_N {
    // non-dimensional functions, i.e.
    void somethingDimensionless(
        const std::array<double, DIM>& dblArray
    ) const;
};

ClassName_[1,2,3].h/cpp

Implements the [1,2,3] dimension version of the class. This should implement the pure virtual functions of ClassName_N in addition to any functions specific to the particular dimension. These addition functions can only be called from other dimensionally specific implementations, otherwise the compiler will complain.

/**
* The dimensionally specific API's
*/
class ClassName_1 : public ClassName_N<1> {
    // dimensional functions, i.e.
    void somethingIn1D() const;
};

Usage

#include <path/to/ClassName.h>

template<int DIM>
void useAnyDimension(const ClassName<DIM>& c) {
    std::array<double, DIM> arr;
    c.somethingDimensionless(arr);
}

void doSomethingIn1D(const ClassName<1>& c) {
    c.somethingIn1D();
}

int main (int argc, char *argv[]) {
    ClassName<1> oneD;
    ClassName<2> twoD;

    // dimension-less 
    useAnyDimension(oneD);
    oneD.somethingDimensionless({0.1});
    twoD.somethingDimensionless({0.1, 0.5});

    // dimension specific
    doSomethingIn1D(oneD);
    oneD.somethingIn1D();

    // the following would give compile-time errors
    //doSomethingIn1D(twoD);
    //twoD.somethingIn1D();

    return 0;
}
\$\endgroup\$
1
\$\begingroup\$

This seems unnecessary complicated. Why not just have one simple class template:

template <int DIM>
class ClassName {
public:
    using array = std::array<double, DIM>;

    void somethingDimensionless(const array& dblArray) const;

    template <int D = DIM,
              typename = std::enable_if_t<(D == 1)>>
    void somethingIn1D() const {
        ...
    }
};

And use it directly:

ClassName<1> oneD;
ClassName<2> twoD;

We use SFINAE instead of a static_assert in case you want to write a type trait to check for the existence of somethingIn1D. With a static_assert, ClassName<2>{}.somethingIn1D() wouldn't compile but overload resultion would still find it.

\$\endgroup\$
  • \$\begingroup\$ Interesting, I hadn't seen std::enable_if before. This will certainly work on some of the smaller classes, but we also have others that have significantly different APIs between dimension for which this would end up being a bit clunky. \$\endgroup\$ – BrT Sep 9 '15 at 8:43
  • \$\begingroup\$ Just to note: std::enable_if_t is only c++14 \$\endgroup\$ – BrT Sep 30 '15 at 8:31
  • \$\begingroup\$ @BrT You can always write the alias yourself in C++11. \$\endgroup\$ – Barry Sep 30 '15 at 10:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.