Reputation: 32923
In Python we can create an "arguments object" to call any function that accept such sequence of arguments:
args = (42, True, "Hello")
f1(*args)
f2(*args)
The problem I'm facing in C++ is that I have a generic function f
with several arguments and with 16 different specializations, and I'm forced to explicitly tell which version to call by reading the value of an enum
:
switch (type) {
case xtype::BOOL:
f<bool>(arg1, arg2, arg3, ..., arg9);
break;
case xtype::INT8:
f<int8_t>(arg1, arg2, arg3, ..., arg9);
break;
case xtype::UINT8:
f<uint8_t>(arg1, arg2, arg3, ..., arg9);
break;
case xtype::INT16:
f<int16_t>(arg1, arg2, arg3, ..., arg9);
break;
...
case xtype::FLOAT:
f<float>(arg1, arg2, arg3, ..., arg9);
break;
}
I was recently given another function, g
, which I also need to call following the same pattern:
switch (type) {
case xtype::BOOL:
g<bool>(arg1, arg2, arg3, ..., arg9);
break;
...
}
What is the easiest way to wrap this type-selection code into some generic function? Or, how can I make my life easier when I need to change one argument of f
or g
? For example:
invoke(type, f, arg1, arg2, arg3, ..., arg9);
invoke(type, g, arg1, arg2, arg3, ..., arg9);
The arguments types of f
and g
may not the same.
I added an answer of my own on how I solved this.
Upvotes: 1
Views: 121
Reputation: 32923
I ended up using a plain old macro to solve my code duplication issue:
#define XYZ_DISPATCH_BY_XTYPE(F, T, ...) \
switch(T) \
{ \
case xtype::BOOL: \
F<bool>(__VA_ARGS__); \
break; \
case xtype::FLOAT: \
F<float>(__VA_ARGS__); \
break; \
....
And adding a new function is relatively easy:
XYZ_DISPATCH_BY_XTYPE(f1, type, a0, a1, a2, ..., a9)
XYZ_DISPATCH_BY_XTYPE(g1, type, q0, q1, q2, ..., q9)
Thank you all on your answers!
Upvotes: 0
Reputation:
With variadic templates you might build a list of types and dispatch accordingly
#include <iostream>
#include <stdexcept>
class TypeIdentifier
{
public:
typedef unsigned Integer;
enum Value
{
Bool = 1,
Int8,
UInt8,
Int32,
UInt32,
Int64,
UInt64,
Float,
Double,
String,
Unknown = 0
};
template <Value ...Ids> struct ListType {};
typedef ListType<
Bool,
Int8,
UInt8,
Int32,
UInt32,
Int64,
UInt64,
Float,
Double,
String,
// Always the last value:
Unknown
>
List;
public:
Integer id;
TypeIdentifier(Integer value = Unknown)
: id(value)
{}
template<typename Functor, typename ... T>
typename Functor::result_type dispatch(const Functor&, T&& ...);
};
// dispatch
// =============================================================================
namespace TypeIdentifierDispatch {
template <typename Functor, TypeIdentifier::Value I, TypeIdentifier::Value ... Ids> struct Evaluate;
template <typename Functor>
struct Evaluate<Functor, TypeIdentifier::Unknown> {
template <typename ... T>
static typename Functor::result_type
apply(TypeIdentifier::Integer id, const Functor&, T&& ... arguments) {
throw std::runtime_error("Unknown Type");
}
};
template <typename Functor, TypeIdentifier::Value I, TypeIdentifier::Value ... Ids>
struct Evaluate {
template <typename ... T>
static typename Functor::result_type
apply(TypeIdentifier::Integer id, const Functor& functor, T&& ... arguments) {
if(id == I) return functor(std::forward<T>(arguments) ...);
else return Evaluate<Functor, Ids...>::apply(id, functor, std::forward<T>(arguments) ...);
}
};
template <typename Functor, TypeIdentifier::Value ... Ids, typename ... T>
inline typename Functor::result_type
evaluate(
TypeIdentifier::Integer id,
const Functor& functor,
TypeIdentifier::ListType<Ids...>,
T&& ... arguments)
{
return Evaluate<Functor, Ids...>::apply(id, functor, std::forward<T>(arguments) ...);
}
} // namespace TypeIdentifierDispatch
template<typename Functor, typename ... T>
typename Functor::result_type TypeIdentifier::dispatch(const Functor& functor, T&& ... arguments) {
return TypeIdentifierDispatch::evaluate(
id,
functor,
TypeIdentifier::List(),
std::forward<T>(arguments) ...);
}
struct Add
{
typedef double result_type;
double operator () (double a, double b) const { return a + b; }
};
int main() {
TypeIdentifier id(TypeIdentifier::Int32);
std::cout << id.dispatch(Add(), 1.0, 1.5) << std::endl;
}
Note: In the code above, a functor is involved. You might use a function signature or a std::function instead. kennytm has written some helpful function-traits (See https://github.com/kennytm/utils/blob/master/traits.hpp)
Upvotes: 1