1

I have a template-heavy code at hand in which classes which are supposed to be used as template parameters to the user code, have different constructor signatures. My problem is, that I have not found a good way to call the constructors of the templated Classes in my user code. A mimimal working example could look like this:

#include <string>
#include <iostream>
#include <memory>

class ShortConstructorInLibrary {
    std::string myName;
    static const int tag = 1;
public:
    ShortConstructorInLibrary(std::string name) :
            myName(name) {
    }
};

class LongConstructorInLibrary {
private:

    int a;
    double b;
public:
    static const int tag = 2;
    LongConstructorInLibrary(int arg1, double arg2) :
            a(arg1), b(arg2) {
    }
};
//above is library code

template<typename T>
class MyClass {
    std::shared_ptr<T> member_p;
    //i want to call right constructor for both cases:
public:
    MyClass() {

        //how do i call the different constructors properly?!
        member_p = std::shared_ptr<T>(new T("test"));
    }

};

int main() {
    MyClass<ShortConstructorInLibrary> obj;     //works
    //MyClass<LongConstructorInLibrary> obj2;   // wrong constructor signature
}

Here I have two classes in a library, one with a long and unrelated constructor signature, one with a short one. I want to be able to use both of them as template parameters. In my userClass I somehow have to define what arguments to pass to the constructors depending on the type passed.

I can not use a simple if() because the compiler will check both signatures and one will be wrong. I can not use c++17 for "if constexpr(){}".

I can pass the template Parameter "ShortConstructorInLibrary" to my class and call its constructor perfectly fine, but when I use the other class it will, of course, fail for a wrong constructor signature. I used an ugly trick so far that i implemented two helper methods where i pass a pointer and then let the two methods implement the constructor calls, but that seems ugly to me. I also fiddled around with std::enable_if<> but did not get very far. @Mohit proposed to use partial template specialization, but in the real world code the Short ConstructorInLibrary class is itself templated with a couple of ...templated template arguments. To give you an idea:

‘class CFEM_LOP<Dune::PDELab::QkLocalFiniteElementMap<Dune::GridView<Dune::DefaultLeafGridViewTraits<const Dune::YaspGrid<2> > >, double, double, 1ul>, EngwHillenKnapp2014<MedicalDataManager<double, Dune::YaspGrid<2> >, Dune::YaspGrid<2> >, CFEM_L2OP<Dune::PDELab::QkLocalFiniteElementMap<Dune::GridView<Dune::DefaultLeafGridViewTraits<const Dune::YaspGrid<2> > >, double, double, 1ul> >, Dune::YaspGrid<2> >’

I fell that trying to specialize my user code will be a hell of a mess.

What is the correct way to implement constructor calls of possibly varying signatures?

any hints would be appreciated!

(ubuntu 16.04, gcc)

  • 1
    You might want to clear the code you've posted, it's twice the snippet. – lubgr Jul 05 '18 at 08:54
  • 1
    If you are using c++17 then use `if constexpr` and `std::is_same` – Mohit Jul 05 '18 at 09:06
  • 1
    if only I could. Then I could just throw that into my user code and the compiler would not check both branches of if and -let-me-do-it- :-) I need an old fashioned way though. – user5925562 Jul 05 '18 at 09:09

2 Answers2

1

Try partial specialization method for achieving this and I used std::make_shared for creating std::shared_ptrs

class ShortConstructorInLibrary
{
    std::string myName;
    static const int tag = 1;
public:
    ShortConstructorInLibrary(std::string name) :
        myName(name)
    {
    }
};

class LongConstructorInLibrary
{
private:

    int a;
    double b;
public:
    static const int tag = 2;
    LongConstructorInLibrary(int arg1, double arg2) :
        a(arg1), b(arg2)
    {
    }
};
//above is library code

template<typename T>
class MyClass
{
    std::shared_ptr<T> member_p;
    //i want to call right constructor for both cases:
public:
    MyClass()
    {

        //how do i call the different constructors properly?!
        member_p = std::make_shared<T>("test");
    }

};

template<>
MyClass<LongConstructorInLibrary>::MyClass()
{
    member_p = std::make_shared<LongConstructorInLibrary>(0, 0.0); // pass you values.
}

int main()
{
    MyClass<LongConstructorInLibrary> obj;     //works
                                                //MyClass<LongConstructorInLibrary> obj2;   // wrong constructor signature
}
Mohit
  • 1,225
  • 11
  • 28
  • 1
    Hi. Thank you for your answer! – user5925562 Jul 05 '18 at 09:38
  • 1
    (...) The problem with partial specialization is, that in my real world code, the two classes used as template parameters are themselves quite complicated templates. To give you an idea: the full type of the shortConstructorClass (in the real world) is: ‘class CFEM_LOP – user5925562 Jul 05 '18 at 09:48
  • 1
    @user5925562 use type alias or typedef to make it shorter. – Mohit Jul 05 '18 at 09:51
1

The constructor of MyClass is supposed to create an object of type T. Assume that each T passed as template parameter has a different constructor signature, so you need to pass the arguments needed for the construction of T to the class MyClass and forward these arguments. This is possible since c++11 with variadic templates, i.e.

template <class T>
struct MyClass
{
  std::shared_ptr<T> member_p;

  template <class... Args>
  MyClass(Args&&... args)
    : member_p(std::make_shared<T>(std::forward<Args>(args)...))
  {}
};

int main() {
  MyClass<ShortConstructorInLibrary> obj1("test");
  MyClass<LongConstructorInLibrary> obj2(1, 2.0);
}

You just need to be carefull, with variadic universal constructor arguments, since this also covers the copy/move constructor. In order to not deactivate these with your written constructor, you have to add a little bit of enable_if code. Since you use Dune, you can simply apply a common pattern:

template <class... Args, Dune::disableCopyMove<MyClass, Args...> = 0>
MyClass(Args&&... args)
  : member_p(std::make_shared<T>(std::forward<Args>(args)...))
{}

The disableCopyMove is a wrapper around an enable_if that produces a substitution failure, in case you pass an MyClass const& or MyClass&& to the constructor, so that copy and move constructors are not hidden by your self-defined constructor. The = 0 is necessary, since the type defined by this enable_if wrapper is int and the = 0 is the default value of this non-type template parameter, so that you don't need to specify it yourself.

spraetor
  • 441
  • 4
  • 13