Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constructors, templates and non-type parameters

I've a class that must depend for some reasons from an int template parameter.
For the same reasons, that parameter cannot be part of the parameter list for the class, instead it is part of the parameter list of its constructor (that is, of course, templated).

Here the problems arose.
Maybe I'm missing something, but I can't see an easy way to provide such a parameter to the constructor, because it cannot be deduced nor explicitly specified.

So far, I've found the following alternatives:

  • put the above mentioned parameter into the parameter list of the class

  • create a factory method or a factory function which can be invoked as an example as factory<42>(params)

  • provide a traits struct to the constructor

I tried to create a (not so) minimal, working example for the last mentioned solution, also in order to explain better the problem.
The class in the example is not a template class for itself, for the key point is the constructor, anyway the real one is a template class.

#include<iostream>
#include<array>

template<int N>
struct traits {
    static constexpr int size = N;
};

class C final {
    struct B {
        virtual ~B() = default;
        virtual void foo() = 0;
    };

    template<int N>
    struct D: public B{
        void foo() {
            using namespace std;
            cout << N << endl;
        }

        std::array<int, N> arr;
    };

 public:
     template<typename T>
     explicit C(T) {
         b = new D<T::size>{};
     }

     ~C() { delete b; }

     void foo() { b->foo(); }

 private:
     B *b;
};

int main() {
    C c{traits<3>{}};
    c.foo();
}

To be honest, none of the solutions above mentioned fits well:

  • moving the parameter into the parameter list of the class breaks completely its design and is not a viable solution

  • a factory method is something I'd like to avoid, but it could solve the issue

  • the traits struct seems to be the best solution so far, but somehow I'm not completely satisfied

The question is pretty easy: is there something I missed out there, maybe an easier, more elegant solution, a detail of the language I completely forgot, or are the three approaches mentioned above the ones from which I must choice?
Any suggestion would be appreciated.

like image 928
skypjack Avatar asked Feb 19 '16 23:02

skypjack


2 Answers

You have to pass in something that can be deduced. The simplest thing to use is just a empty wrapper for an int: std::integral_constant. Since you only want ints I believe, we can alias it and then only accept that specific type:

template <int N>
using int_ = std::integral_constant<int, N>;

Where your C constructor just accepts that:

 template <int N>
 explicit C(int_<N> ) {
     b = new D<N>{};
 }

 C c{int_<3>{}};

You could even go all out and create a user-defined literal for this (a la Boost.Hana) so that you can write:

auto c = 3_c; // does the above

Also, consider simply forwarding the trait through to D. Metaprogramming works better if everything everywhere is a type. That is, still accept the same int_ in C:

template <class T>
explicit C(T ) {
    b = new D<T>{};
}

Where now D expects something that has a ::value:

template <class T>
struct D: public B{
    static constexpr int N = T::value;

    void foo() {
        using namespace std;
        cout << N << endl;
    }

    std::array<int, N> arr;
};

It's the same thing either way from the user of C's perspective, but just worth a thought.

like image 189
Barry Avatar answered Oct 19 '22 18:10

Barry


I think that solution with "traits" is the best for most cases.

Only to make a little more "mess" in this issue I will provide two more alternatives. Maybe in some very specific cases - they can be in some way better.


  1. Template global variable - you can name it a prototype solution:

class C only differs in its constructor from your original code:

class C final {
    // All B and D defined as in OP code
 public:
    // Here the change - c-tor just accepts D<int> 
    template <int size>
    explicit C(D<size>* b) : b(b) {}

    // all the rest as in OP code
};

The prototype - template global variable:

template <int N>
C c{new C::D<N>()}; 
// this variable should be rather const - but foo() is not const 
// and copy semantic is not implemented...

And usage:

int main() {
    // you did not implement copy semantic properly - so just reference taken
    C& c = ::c<3>; 
    c.foo();
}

  1. Solution with Base class - and derive class depending on int

This solution, although looks pretty promising I would personally avoid - that only complicates design - and some possibility of object slicing is here present too.

class CBase {
    // all here as in OP code for C class
public:
    // only difference is this constructor:
    template<int size>
    explicit CBase(D<size>* b) : b(b) {}
};

Then - the final class:

template <int N>
class C final : private CBase {
public:
    C() : CBase(new CBase::D<N>()) {}
    using CBase::foo;
};

The usage:

int main() {
    C<3> c;
    c.foo();
}

Q: One can ask in which way solution with Base class is better than just adding int as another parameter.
A: by base implementation class you do not need to have many "copies" of the same code - you avoid template code bloat...

like image 2
PiotrNycz Avatar answered Oct 19 '22 18:10

PiotrNycz