Constructors, templates and non-type parameters
Asked Answered
C

3

17

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.

Champac answered 19/2, 2016 at 23:41 Comment(9)
It can be deduced, but yes, you need a tag type - e.g., template<int N> explicit C(traits<N>); (Where traits can be template<int N> using traits = std::integral_constant<int, N>;)Demetrius
Yeah, that's almost what I did. Anyway, if I must introduce a traits class, I can use it also to define a few other things, and that's why I didn't use something like integral_constant.Champac
In your initial paragraph, you say that it's both a template parameter and a parameter to the constructor, which doesn't make sense. Or is it this very contradiction that's puzzling you?Tracery
I mean that it would be part of the parameter list of the template declaration of the constructor if it had been easily deducible, as template<int N> constructor(whatever, you, want).Champac
I'm not 100% clear what you're asking, but is there any kind of type erasure trick that would help?Mccandless
It might be, not sure, but feel free to add an answer if you have an idea.Champac
Related to #2862339Coquille
Like Ulrich, I think you should better explain where the requirements come from that you have summarized in your initial paragraph, because as stated it is hard to infer what you're really trying to do.Aerosol
Unfortunately it's so that programming language itself can be tailored in any way, in which originally authors of programming language did not thought about. Such misusing or abusing language is possible for extreme programmers who think they have reached Gaia and can do whatever they want with the language. Unfortunately such code becomes complex to maintain and develop further, and it's highly possible that during next iterations another developer will most probably rewrite your solution. May be you can specify in more details what you want to achieve at the end ?Kingbolt
A
7

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.

Angle answered 29/3, 2016 at 2:6 Comment(0)
L
2

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...

Liven answered 30/3, 2016 at 14:40 Comment(0)
W
-2

use template specialization and inheritance:

#include <iostream>
using namespace std;

template <int num> struct A {
    A() { cout << "generic" << endl; }
};

template <> struct A<1> {
    A() { cout << "implementation of 1" << endl; }
};

template <int num>
struct B : public A<num> {
    B() : A<num>() {}
};

int main(int argc, char *argv[])
{
    B<1> b;
    B<3> b1;
    B<4> b2;
}

edit: or you can do it even easier:

template <int num>
struct A {
    A();
};

template <int num>
A<num>::A() { cout << "general " << num << endl; }

template <>
A<1>::A() { cout << "specialization for 1" << endl; }
Washday answered 31/3, 2016 at 5:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.