Originally asked here: Dynamically call lambda based on stream input
The following version is based heavily on the answer provided by @iavr though I hope that I have done enough more to mkae it worth reviewing again.
A way to clean parameters of all declarations:
template<typename Decorated>
struct CleanType
{
typedef typename std::remove_reference<Decorated>::type NoRefType;
typedef typename std::remove_const<NoRefType>::type BaseType;
};
Get Caller traits of a functor:
template <typename T>
struct CallerTraits
: public CallerTraits<decltype(&T::operator())>
{};
template<typename C, typename ...Args>
struct CallerTraits<void (C::*)(Args...) const>
{
static constexpr int size = sizeof...(Args);
typedef typename std::make_integer_sequence<int, size> Sequence;
typedef std::tuple<typename CleanType<Args>::BaseType...> AllArgs;
};
// Notice the call to `CleanType` to get the arguments I want.
// So that I can create objects of the correct type before passing
// them to the functor:
The actual calling of the functor based on reading data from a stream (via ResultSetRow (but I am sure that can be easily generalized)).
Done in 2 parts:
This is the part you call:
template<typename Action>
void callFunctor(Action action, Detail::ResultSetRow& row)
{
// Get information about the action.
// And retrieve how many parameters myst be read from
// the stream before we call `action()`
typedef CallerTraits<decltype(action)> Trait;
typedef typename Trait::Sequence Sequence;
doCall2(action, row, Sequence());
}
Part 2:
Here we extract the parameters from the stream then call the action.
template<typename Action, int... S>
void doCall2(Action action, Detail::ResultSetRow& row, std::integer_sequence<int, S...> const&)
{
// Create a tupple that holds all the arguments we need
// to call the functior `action()`
typedef CallerTraits<decltype(action)> Trait;
typedef typename Trait::AllArgs ArgumentTupple;
// Use the template parameter pack expansion
// To read all the values from the stream.
// And place them in the tuple.
ArgumentTupple arguments(row.getValue1<typename std::tuple_element<S, ArgumentTupple>::type>()...);
// Use the template parameter pack expansion
// To call the function expanding the tupple into
// individual parameters.
action(std::get<S>(arguments)...);
}
Edit 1:
Example usage:
// Using `ResultRowSet` from below.
int main()
{
std::stringstream stream("1 Loki 12.3 2.2");
Detail::ResultRowSet row(stream);
callFunctor([](int ID, std::string const& person, double item1, float item2){
std::cout << "Got Row:"
<< ID << ", "
<< person << ", "
<< item1 << ", "
<< item2 << "\n";
}, row);
}
Here it will read an int
(ID), a std::string
(person) a double
(item1) and a float
(item2) from the stream (represented by row). Then call the lambda provided.
This is not the actual implementation of Detail::ResultSetRow
. But for code review purposes you can think of it as:
namespace Detail
{
class ResultRowSet
{
std::istream& stream;
public:
ResultRowSet(std::istream& s)
: stream(s)
{}
template<typename T>
T getValue1()
{
T val;
stream >> val;
return val;
}
};
}