Reputation: 6477
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
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
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
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
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
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