Skip to main content
3 of 6
added 11 characters in body; edited tags
Jamal
  • 34.9k
  • 13
  • 133
  • 237

Dynamically call lambda based on stream input

Some context: I have code that looks like this (minor issue noted here):

Statement  select("SELECT * FROM People WHERE ID > ? AND ID < ?");
select.execute(1462, 1477, [](int ID, std::string const& person, double item1, float item2){
     std::cout << "Got Row:" 
               << ID     << ", " 
               << person << ", " 
               << item1  << ", " 
               << item2  << "\n";
 });

Anyway this connects to the MySQL DB and starts pulling data from the server. So inside execute I loop over the results and call the lambda for each row:

    template<typename Action, typename ...Args>
    void execute(Args... param, Action action)
    {
        // STUFF TO SET up connection.
        // Start retrieving rows.


        while(row = results->getNextRow())
        {
            call(action, row);
        }
    }

So here row gets a single row from the socket connection with mysql (so it calls the lambda as it receives each row (no pulling the rows into memory first)). So the code I want to review is pulling the data and calling the lambda.

 // Statement::call

    template<typename Action>
    void call(Action action, std::unique_ptr<ResultSetRow>& row)
    {
        typedef CallerTraits<decltype(action)>   trait;
        typedef typename trait::AllArgs         AllArgs;
        Caller<trait::size, 0, AllArgs, Action>::call(action, row);
    }

This utilizes the helper class CallerTraits and Caller to pull the required rows from the stream and then call the lambda:

// CallerTraits
// Get information about the arguments in the lambda

template <typename T>
struct CallerTraits
    : public CallerTraits<decltype(&T::operator())>
{};

template<typename C, typename ...Args>
struct CallerTraits<void (C::*)(Args...) const>
{
    static const int                        size = sizeof...(Args);
    typedef std::tuple<Args...>             AllArgs;
};

Then the Caller:

// Caller::call()
//    Reads the next argument required by the lambda from the stream.
//    An exception will be generated if the next argument on the stream
//    does not match the type expected by the lambda.
template<int size, int index, typename ArgumentTupple, typename Action, typename ...Args>
struct Caller
{
    static void call(Action action, std::unique_ptr<ResultSetRow>& row, Args... args)
    {
        // Get the next argument type required by the lambda.
        // As defined by index. Then remove all ref and const
        // bindings.
        typedef typename std::tuple_element<index, ArgumentTupple>::type    NextArgBase;
        typedef typename std::remove_reference<NextArgBase>::type           NextArgCont;
        typedef typename std::remove_const<NextArgCont>::type               NextArg;

        // Read the next value from the stream.
        NextArg val;
        row->getValue(val);

        // Recursively call Caller::call() (via doCall())
        // To get the next argument we need. All the arguments
        // are accumulated in the var args parameter `args`
        doCall<size-1, index+1, ArgumentTupple>(action, row, args..., val);
    }
};

Specialization when no more args need to be retrieved:

// Specialization of Caller::call() when we have got all the arguments.
// This simply calls the lambda with the arguments we have accumulated.
template<int index, typename ArgumentTupple, typename Action, typename ...Args>
struct Caller<0, index, ArgumentTupple, Action, Args...>
{
    static void call(Action action, std::unique_ptr<ResultSetRow>&, Args... args)
    {
        action(args...);
    }
};

Function to deduce parameter types:

// Function template needed because we
// can not deduce the Args... parameter manually in the call.
// so we let the compiler deduce it for us.
template<int size, int index, typename ArgumentTupple, typename Action, typename ...Args>
void doCall(Action action, std::unique_ptr<ResultSetRow>& row, Args... args)
{
    Caller<size, index, ArgumentTupple, Action, Args...>::call(action, row, args...);
}
Loki Astari
  • 96.6k
  • 5
  • 125
  • 338