2

(I had no idea how to name this question and I couldn't find anything similar. Sorry if this is duplicate)

If I want to inherit from some base template class, I can do this that way:

template<typename A=int, typename B=char> class C {};
template<typename... Args> class D : public C<Args...> {}; //it works!

This way I can change in project passed parameters to template class C and I don't have to change every usage of class D. Great. But what if I have template class using not only types as parameters but also values? For example:

template<int dim=3, typename float_t=double> class GeometricObject{};
template<typename... Args> class Point : public GeometricObject<Args...>{}; //it doesnt work

Of course I could define last template with integer type on the beginning. But this is not a way, if I would have 100 different classes all inheriting from GeometricObject and then I would change default dim value to 2, I would have to change every single class definition.

I also hope that there is the way without using any #define, #else and similar preprocessor commands. I know that templates are in fact also preprocessor commands, but... well, let's be modern here ;)

Ch3shire
  • 1,095
  • 2
  • 14
  • 39

3 Answers3

3

You can not mix type and non-type parameters in a template parameter pack. But it seems that your Point and other derived classes don't need to access the parameter pack arguments separately. In such cases it's easier, as well as more semantically correct, to pass the base class:

template<int dim=3, typename float_t=double> class GeometricObject{};
template<class GeometricObject=GeometricObject<>> class Point : public GeometricObject{};

Instantiating a Point could then look like:

Point<> a{}; // the same as Point<GeometricObject<>> a{};
Point<GeometricObject<4>> b{};
Point<GeometricObject<2, float>> c{};

Of course the GeometricObject<...> could be typedef'd to something shorter. Also, it can be made to look like a namespace instead of providing parameters to each geometric object separately:

template<int dim = 3, typename float_t = double>
struct GeometricObjects {
  using Base = GeometricObject<dim, float_t>;
  using Point = ::Point<Base>;
  // ...
};

using TwoDim = GeometricObjects<2>;
TwoDim::Point a{};
The Vee
  • 11,420
  • 5
  • 27
  • 60
  • What wizardry is this o.O I thought that `using` statements was the domain of C#. Thanks for response! – Ch3shire Dec 19 '16 at 14:06
  • 1
    `using TwoDim = GeometricObjects<2>` is the same as `typedef GeometricObjects<2> TwoDim`, just more "modern" :-) – The Vee Dec 19 '16 at 14:07
  • 1
    Which part looks like wizardry? I'll be happy to elaborate. – The Vee Dec 19 '16 at 14:08
  • Just `using` statements in a way as you use types of variables. It's almost like finding python-like smart shortcuts in C++. – Ch3shire Dec 19 '16 at 14:11
  • Thanks for the improvement. I adapted the subsequent text to it. Here's a link for the `using` syntax here on SO: http://stackoverflow.com/questions/10747810/what-is-the-difference-between-typedef-and-using-in-c11 – The Vee Dec 19 '16 at 14:24
2

I suppose you have multiple template classes and you want your Point object to be able to inherit from them all.

Instead of doing:

template <typename ... Args>
class Point : public GeometricObject<Args...>{};

I would instead do:

template <typename T>
class Point : public T {};

Now we just have to define proper traits to access the types template parameters in case they are needed. These types should be factored into a std::tuple (for instance).

The burden to fill this trait is on the GeometricObject class. For example, with your definition we would have:

template <typename T>
struct type_parameters;

template <int N, typename Float>
struct type_parameters<GeometricObject<N, Float> {
    typedef std::tuple<Float> types;
};

The main scenario: a method of Point needs the type template parameters of GeometricObject (to forward them to a method of GeometricObject). To achieve this, you will have to pass in a tuple that will be unfold to call the inner method. To do so I make use of features added in the STL for C++14. You could still rewrite them yourself but I spared me the hassle for this question...

template <typename T>
class Point : public T {
    template <typename Method, typename ... Args, std::size_t ... Is>
    auto call_unfold(Method method, std::tuple<Args...> const& tuple, std::integer_sequence<std::size_t, Is...>) {
        return (this->*method)(std::get<Is>(tuple)...);
    }
    template <typename Method, typename Tuple>
    auto call_unfold(Method method, Tuple const& tuple) {
        return call_unfold(method, tuple, std::make_index_sequence<std::tuple_size<Tuple>::value>());
    }
public:
    typedef typename type_parameters<T>::types types;
    void some_method(types const& args) {
        return call_unfold(&T::some_method, args);
    }
};

This example is quite meaningless but the same technique could be useful with constructors of Point that need to call a base class constructor.

A live demo showing how it works is available on Coliru

Rerito
  • 5,886
  • 21
  • 47
  • Thank you very much for answer. Looks like I don't have an option, I need to make more complex code if I want to mess with templates. Thanks! :) – Ch3shire Dec 19 '16 at 15:52
0

Ok, so I figured it out how I should include variable-type template parameters into tuples. Basically I need to 'encapsulate' them into new parameter. This example works perfectly well AND solves my problem:

#include <type_traits>

template<int n = 2> struct Dim {
    const int dim = n;
};

template<typename T> class SillyBaseClass {
public:
    typedef typename T dim;
};

template<typename... Args> class SillyDerivedClass : public SillyBaseClass<Args...>{
public:
    typedef typename SillyBaseClass::dim dim;
    SillyDerivedClass() {
        static_assert(std::is_same<dim,Dim<2>>::value,
            "Number of dimensions must be equal to 2");
    }
};

int main() {

    SillyDerivedClass<Dim<2>> Class2d;    //this works
    SillyDerivedClass<Dim<3>> Class3d;    //this returns expected error

}
Ch3shire
  • 1,095
  • 2
  • 14
  • 39