7
\$\begingroup\$

Based on Dynamically call lambda based on stream input: Try 2

This fixes most of the points pointed out by @Morwenn.

It has been generalized so anybody can call it.
The only extra part needed by the code is a function to retrieve values from a stream. This is passed as a template argument to callFunctor() see the example below.

An example of how to use it:
Here I use std::stringstream as the input to be used for this example. In my own version I have a DB socket streaming rows from a MySQL DB and the lambda is called for each row. The point is the current version of callFunctor() can be used in any general environment (I hope somebody finds it useful).

main.cpp

#include "detail_caller.h"
#include <iostream>
#include <sstream>

struct GetStreamValue
{
    // The input type to this method must match
    // the type of the first argument to `callFunctor`
    // As you will be passed the stream and asked to pull
    // a specific type from it.
    template<typename R>
    static R get(std::istream& stream)
    {
        R   result;
        stream >> result;
        return result;
    }
};

int main()
{
    std::stringstream       data("10 13 Loki 4.5");

    callFunctor<GetStreamValue>(
        data,
        [](int val1, int val2, std::string const& val3, float val4)
        {
            std::cout << "Got(" << val1 << ", "
                                << val2 << ", "
                                << val3 << ", "
                                << val4 << ")\n";
        }
    );
}

detail_caller.h

#ifndef THORSANVIL_DETAIL_CALLER_H
#define THORSANVIL_DETAIL_CALLER_H

#include <utility>

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 std::make_integer_sequence<int, size>               Sequence;
    typedef std::tuple<std::decay_t<Args>...>                   AllArgs;
};

template<typename StreamValueGetter, typename Action, typename Stream, int... S>
void expandArgsAndCallFunctor(Stream& stream, Action action, std::integer_sequence<int, S...>)
{
    typedef CallerTraits<decltype(action)>  Trait;
    typedef typename Trait::AllArgs         ArgumentTuple;

    // ArgumentTuple  arguments(StreamValueGetter::template get<typename std::tuple_element<S, ArgumentTuple>::type>(stream)...);
    // action(std::get<S>(arguments)...);

    // The above two commented out lines
    // Represent the logic of the following line which retrieves
    // and calls the arguments in a single line thus avoiding temporaries.
    action(StreamValueGetter::template get<typename std::tuple_element<S, ArgumentTuple>::type>(stream)...);
}

template<typename StreamValueGetter, typename Action, typename Stream>
void callFunctor(Stream& stream, Action action)
{
    typedef CallerTraits<decltype(action)>  Trait;
    typedef typename Trait::Sequence        Sequence;

    expandArgsAndCallFunctor<StreamValueGetter>(stream, action, Sequence());
}

#endif
\$\endgroup\$
6
  • \$\begingroup\$ Which C++14-only features, if any, are you using? \$\endgroup\$ Commented Apr 6, 2014 at 17:23
  • \$\begingroup\$ Apparently std::decay_t and std::integer_sequence \$\endgroup\$ Commented Apr 6, 2014 at 17:24
  • \$\begingroup\$ std::decay_t is part of C++11: en.cppreference.com/w/cpp/types/decay \$\endgroup\$
    – Yuushi
    Commented Apr 7, 2014 at 1:12
  • 1
    \$\begingroup\$ @Yuushi: c++11 => std::decay c++14 => std::decay_t \$\endgroup\$ Commented Apr 7, 2014 at 3:07
  • \$\begingroup\$ Ah, you're right, my mistake. \$\endgroup\$
    – Yuushi
    Commented Apr 7, 2014 at 3:21

1 Answer 1

4
\$\begingroup\$

I was about to add more things to my answer on your previous post and noticed meanwhile that your already asked a following question (that's almost too fast! :o). I have done some additional reading about C++14 and integer sequences. And I have found some things in the standard that could help to improve your code again.

I realized at some point that you were using int as the value_type of std::integer_sequence. However, you initialize your static constexpr int size with the result of sizeof... and sizeof... returns a std::size_t, not an int.

Then I remembered that, along with std::integer_sequence, there is a typedef for std::integer_sequence<std::size_t, N> which is called std::index_sequence and which has been made to solve the kind of problem that your are trying to solve. Therefore, you should every occurrence of int by std::size_t and of std::integer_sequence<int, N> by std::index_sequence<N>.


Moreover, you don't need to create a typedef for Sequence in CallerTraits: you can deduce this class directly in callFunctor thanks to CallerTraits<>::size:

template<typename StreamValueGetter, typename Action, typename Stream>
void callFunctor(Stream& stream, Action action)
{
    using Sequence = std::make_index_sequence<CallerTraits<Action>::size>;
    expandArgsAndCallFunctor<StreamValueGetter>(stream, action, Sequence());
}

Note that I also replaced decltype(action) by Action since it is the same type.


Also, you can get rid of the public inheritance specifier for CallerTraits since you are using a struct. That does not change anything whatsoever.


Here is your code once refactored. I don't provide a link to an online compiler since none of the ones available has a good enough C++14 support (you just need to add some light library components though):

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

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

template<typename StreamValueGetter, typename Action, typename Stream, std::size_t... S>
void expandArgsAndCallFunctor(Stream& stream, Action action, std::index_sequence<S...>)
{
    typedef CallerTraits<Action>        Trait;
    typedef typename Trait::AllArgs     ArgumentTuple;

    // ArgumentTuple  arguments(StreamValueGetter::template get<typename std::tuple_element<S, ArgumentTuple>::type>(stream)...);
    // action(std::get<S>(arguments)...);

    // The above two commented out lines
    // Represent the logic of the following line which retrieves
    // and calls the arguments in a single line thus avoiding temporaries.
    action(StreamValueGetter::template get<std::tuple_element_t<S, ArgumentTuple>>(stream)...);
}

template<typename StreamValueGetter, typename Action, typename Stream>
void callFunctor(Stream& stream, Action action)
{
    using Sequence = std::make_index_sequence<CallerTraits<Action>::size>;
    expandArgsAndCallFunctor<StreamValueGetter>(stream, action, Sequence());
}
\$\endgroup\$
2
  • \$\begingroup\$ The choice to use int over std::size_t was deliberate. I am trying not to use unsigned types for anything but bitfields. \$\endgroup\$ Commented Apr 6, 2014 at 18:40
  • \$\begingroup\$ @LokiAstari I understand your choice. Too bad that the standard only makes things easier for unsigned types :/ \$\endgroup\$
    – Morwenn
    Commented Apr 6, 2014 at 18:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.