Jouni Helske
Jouni Helske

Reputation: 6477

Class data member type based another data member?

Let's say I have a base class foo and two derived classes A and B. I then have another class bar, which has a data members x, y, z, which can be either A,or, but the types depends on other data members x_type, y_type, and z_type, and those values are not available at compile time. I though about using template data member and defining the type in constructor, where I get the values for the types but apparently that is not possible at least in C++11. So how to proceed?

class foo{
public:
  foo(double);
  int x_type;
  virtual double do_something(double, int) = 0;
};

class A: public foo {
public:
  A(double, double);
  double do_something(double, int);

private:
  double z1;
  double z2;
};

class B: public foo {
public:
  B(double);
  double do_something(double, int);

private:
  double w;
};

class bar {
public:
  bar();
  double do_something2(int);

private:
  int x_type;
  int y_type;
  int x_type;
  x; // these are either A or B...
  y;
  z;
};

And in constructor I would have something like

if(x_type == 1){
  x = A(arg1, arg2);
} else {
  x = B(arg3);
}

In my real application there can be much higher number of derived classes and data members with unknown types. I was wondering if it is possible to make bar a template class with multiple template parameters, but I am not sure if that is possible either as the parameter type depends on another parameter?

Upvotes: 3

Views: 169

Answers (5)

max66
max66

Reputation: 66190

If all type that can be used for x, y and z are all derived from a common base class, the base-pointer solution, with std::unique_ptr (+1 for Lyubomir Stankov), is (IMHO) a good solution.

But you asked "if it is possible to make bar a template class with multiple template parameters".

Yes: it's possible. Not really elegant (ever IMHO) but possible.

I propose the following solution for fun but I think that, in a more general case (note that, in my example, A and B are unrelated classes, not more derived from foo), can be useful (I hope so)

#include <tuple>
#include <string>
#include <utility>

class A
 {
   private:
      double       d;
      std::string  s;

   public:
      A (double d0, std::string s0) : d { d0 }, s { s0 } { }
 };

class B
 {
   private:
      long l;

   public:
      B (long l0) : l { l0 } { }
 };

template <typename Tx, typename Ty, typename Tz>
class bar
 {
   private:

      template <typename ... Ts>
         using tpl = std::tuple<Ts...>;

      template <std::size_t ... Is>
         using is = std::index_sequence<Is...> const;

      template <std::size_t N>
         using mis = std::make_index_sequence<N>;

      Tx  x; 
      Ty  y;
      Tz  z;

      template <typename ... Tsx, std::size_t ... Isx,
                typename ... Tsy, std::size_t ... Isy,
                typename ... Tsz, std::size_t ... Isz>
      bar (tpl<Tsx...> const & tx0, is<Isx...> const &,
           tpl<Tsy...> const & ty0, is<Isy...> const &,
           tpl<Tsz...> const & tz0, is<Isz...> const &)
         : x { std::get<Isx>(tx0) ... },
           y { std::get<Isy>(ty0) ... },
           z { std::get<Isz>(tz0) ... }
       { }

   public:

      template <typename ... Tsx, typename ... Tsy, typename ... Tsz>
      bar (tpl<Tsx...> const & tx0,
           tpl<Tsy...> const & ty0,
           tpl<Tsz...> const & tz0)
         : bar(tx0, mis<sizeof...(Tsx)> {},
               ty0, mis<sizeof...(Tsy)> {},
               tz0, mis<sizeof...(Tsz)> {})
       { }
 };

int main()
 { 
   bar<A, B, A>  aba{ std::make_tuple(2.3, "str1"),
                      std::make_tuple(4),
                      std::make_tuple(5.4, "str2") };

   bar<B, A, B>  bab{ std::make_tuple(3),
                      std::make_tuple(3.2, "str3"),
                      std::make_tuple(5) };
 }

Unfortunately this example use std::make_index_sequence and std::index_sequence that are C++14 features.

If you want implement foo in C++11, you can implement the following structs struct indexSeq and struct indexSeqHelper, to substitute std::index_sequence and std::make_index_sequence

template <std::size_t ...>
struct indexSeq
 { };

template <std::size_t N, std::size_t ... Next>
struct indexSeqHelper
 { using type = typename indexSeqHelper<N-1U, N-1U, Next ... >::type; };

template <std::size_t ... Next >
struct indexSeqHelper<0U, Next ... >
 { using type = indexSeq<Next ... >; };

and define is and mis as follows

  template <std::size_t ... Is>
     using is = indexSeq<Is...>;

  template <std::size_t N>
     using mis = typename indexSeqHelper<N>::type;

Upvotes: 2

W.F.
W.F.

Reputation: 13988

I though about using template data member and defining the type in constructor, where I get the values for the types but apparently that is not possible at least in C++11

C++11 comes with the standard schema of dealing with templated construction depending on some parameter by using make_* template function creating appropriate type of object. See e.g. make_tuple function:

auto t = std::make_tuple(1, "abc", 1.0); 
// decltype(t) == std::tuple<int, char const*, double>

This can be implemented by creating template class and template construction function:

template <class T>
struct foo { 
   T t; 
   foo(T t): t(t) { }  
};

template <class T>
foo<T> make_foo(T t) { return foo<T>(t); }

Upvotes: 1

Adrian Colomitchi
Adrian Colomitchi

Reputation: 3992

I was wondering if it is possible to make bar a template class with multiple template parameters, but I am not sure if that is possible either as the parameter type depends on another parameter?

I don't know if this helps, but I'll let it here just in case.

You see, different specializations of a template can inherit from different classes. So that you can have:

// fwd decl
template <int kind> class bar;

template <> class bar<1> : public A {
public:
  bar(double x, double y) : A(x,y) { }
};

template <> class bar<2> : public B {
public:
  bar(double a) : B(a) { }
};

At later stage, when you come with a class C : public foo, you just assign another kind to a new bar template specializations and there you have it: using bar as a unifying name (warning... but not a unifying type - not other than the common foo ancestor. bar<1> and bar<2> will be two different types)

So, Ok, if you don't want inheritance, you can have it by different has-a in specific bar template specializations.

Like

template <int kind> class bar;

template <> class bar<1> {
   A val;
public:
  bar(double x, double y) : val(x,y) { }

  void doSomething2(...) {
    // use the val of type A
  }
};
template <> class bar<2> {
   B val;
   double y_;
public:
  bar(double x, double y) : val(x), y_(y) { }

  void doSomething2(...) {
    // use the val of type B and a separate y_
  }
};

Upvotes: 1

Lyubomir Stankov
Lyubomir Stankov

Reputation: 36

You need to use polymorphism and take advantage of the common base class Foo:

private:
  int x_type;
  int y_type;
  int x_type;
  std::unique_ptr<Foo> x; // these are either A or B...
  std::unique_ptr<Foo> y;
  std::unique_ptr<Foo> z;

};

Then in your constructor you can create x y z from the correct type:

if(x_type == 1){
  x.reset(new A(arg1, arg2));
} else {
  x.reset(new B(arg3));
}

It is a good practice to move the code that creates the correct Foo instance in a so called "factory" class or function in order to hide the decision making logic and construction specifics (which might be quite complex at times).

Upvotes: 2

Miles Budnek
Miles Budnek

Reputation: 30494

The static type of all variables must be known at compile time, so it cannot change based on the value of a run time object. The way to make this work is to make x, y, and z all have the type std::uniqe_ptr<foo> and then dynamically allocate an A or B object at run time:

class bar {
public:
    bar(some_type something) {
        if (something == some_value) {
            b.x = new A(3.14, 12.34);
        } else {
            b.x = new B(456.78);
        }
    }
private:
    int x_type;
    std::unique_ptr<foo> x;
    //...
};

int main() {
    bar b(whatever());
}

In this case you should also declare foo::~foo() to be virtual so that you ensure the derived objects get destroyed correctly.

It would also be a generally Good Idea™ to eliminate x_type and friends entirely and write code that doesn't care about the actual type of x once it's created.

Upvotes: 1

Related Questions