Reputation: 1429
I have two enums which basically determine (on runtime) what to do. The 'mapping' looks something like
struct Foo { class CA; class CB; class CC; CA a; CB b; CC c; };
enum Base { A, B, C };
enum Func { X, Y };
Foo foo;
// A, X => use(foo.a.x());
// A, Y => use(foo.a.y());
// B, X => use(foo.b.x());
// B, Y => use(foo.b.y());
The problem is, that a
, b
and C
, as well as the return types of x()
and y()
are all of different types (some really huge template types).
Mapping the two enums using switches or ifs is pretty ugly and requires lots of effort so I wondered, if I could somehow write something like this:
struct Foo { class CA; class CB; class CC; CA a; CB b; CC c; };
enum Base { A, B, C, };
enum Func { X, Y, };
template <typename T> auto applyFunc(Func f, T t)
{
switch(f)
{
case X: return t.x();
case Y: return t.y();
}
}
auto getBase(Base b, Foo f)
{
switch(b)
{
case A: return f.a;
case B: return f.b;
case C: return f.c;
}
}
Func f;
Base b;
Foo foo;
use(applyFunc(f, getBase(b, foo)));
Edit 1:
I can not edit the classes CA
, CB
and CC
. I also can not edit the classes/return types of x()
and y()
. All those types come from an external library.
Upvotes: 5
Views: 15241
Reputation: 275896
You can use continuation passing style.
template <class F> void applyFunc(WhichFunc w, T t, F f)
{
switch(w)
{
case X: f(t.x());
case Y: f(t.y());
}
}
template<class F>
void getBase(Base b, Foo foo, F f)
{
switch(b)
{
case A: f(foo.a);
case B: f(foo.b);
case C: f(foo.c);
}
}
Where instead of returning, you pass the next step as a parameter to the previous.
Func f;
Base b;
Foo foo;
getBase(b, [&](auto&&b){ applyFunc( f, b, [&](auto&& r){ use(r); } ); } );
or somesuch (there may be typos).
std::variant
(or boost) can be used to move the continuation until after the return value.
auto b = getBase(b);
auto r = visit( b, [&](auto&& b){ return applyFunc( f, b ); } );
visit( r, [](auto&& r){ use(r); } );
where instead of taking a continuation, each of the above returns a variant<possible_return_types_go_here>
.
Upvotes: 7
Reputation: 50568
Another solution is using traits:
struct Foo { class CA {}; class CB {}; class CC {}; CA a; CB b; CC c; };
enum Base { A, B, C, };
enum Func { X, Y, };
template<Base> struct GetBaseTraits;
template<> struct GetBaseTraits<Base::A> { using type = Foo::CA; };
template<> struct GetBaseTraits<Base::B> { using type = Foo::CB; };
template<> struct GetBaseTraits<Base::C> { using type = Foo::CC; };
template<Base b>
typename GetBaseTraits<b>::type getBase(Foo f);
template<> typename GetBaseTraits<A>::type getBase<A>(Foo f) { return f.a; }
template<> typename GetBaseTraits<B>::type getBase<B>(Foo f) { return f.b; }
template<> typename GetBaseTraits<C>::type getBase<C>(Foo f) { return f.c; }
int main() {
Foo f{};
Foo::CA ca = getBase<A>(f);
Foo::CB cb = getBase<B>(f);
Foo::CC cc = getBase<C>(f);
}
Upvotes: 1
Reputation: 218268
You may use tag dispatching if arguments are known at compile time:
template <Base> BaseTag{};
decltype(auto) getBaseImpl(Foo& foo, BaseTag<Base::A>) { return foo.a; }
decltype(auto) getBaseImpl(Foo& foo, BaseTag<Base::B>) { return foo.b; }
decltype(auto) getBaseImpl(Foo& foo, BaseTag<Base::C>) { return foo.c; }
template <Base base>
decltype(auto) getBase(Foo& foo) { return getBaseImpl(foo, BaseTag<base>{}); }
template <Func> FuncTag{};
template <typename T>
decltype(auto) getFuncImpl(T& t, FuncTag<Func::X>) { return t.x(); }
template <typename T>
decltype(auto) getFuncImpl(T& t, FuncTag<Func::Y>) { return t.y(); }
template <typename T, Func func>
decltype(auto) getFunc(T& t) { return getFuncImpl(f, FuncTag<func>{}); }
And finally:
template <Base base, Func func>
decltype(auto) getElem(Foo& foo) { return getFunc<func>(getBase<base>(foo)); }
Upvotes: 0
Reputation: 32923
A function should always return values with the same data type, determined at compile time. What you can do is use some form of variant, for example boost::variant, or create your own variant type:
enum ReturnType {
RET_STR, RET_INT, RET_DBL
};
struct variant {
ReturnType valtype;
string str;
int i;
double d;
};
string f1() { ... }
int f2() { ... }
double f3() { ... }
variant f(int x) {
variant v;
switch (x) {
case 0:
v.valtype = RET_STR;
v.str = f1();
break;
case 1:
v.valtype = RET_INT;
v.str = f2();
break;
case 2:
v.valtype = RET_DBL;
v.str = f3();
break;
}
return v;
}
Alternatively, you can use some form of polymorphism:
class result_handler {
public:
virtual ~result_handler(){}
virtual void handle_string(string s) = 0;
virtual void handle_int(int i) = 0;
virtual void handle_double(double d) = 0;
};
void f(int x, result_handler* h) {
switch (x) {
case 0:
h->handle_string(f1());
break;
case 1:
h->handle_int(f2());
break;
case 2:
h->handle_double(f3());
break;
}
}
class my_result_handler : public result_handler {
public:
virtual void handle_string(string s) { cout << "string " << s << endl; }
virtual void handle_int(int i) { cout << "int " << i << endl; }
virtual void handle_double(double d) { cout << "double " << d << endl; }
};
Upvotes: 0
Reputation: 9735
In short: you can not. The return type of a function must to be well-known a compile time. Indeed, trying to return different types within a function with auto
return type, it will give you a compiler error, even if you use template.
Probably your solution consists into the usage of polymorfism.
Upvotes: 1