11
\$\begingroup\$

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 make 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)...);
}

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;
            }
    };
}
\$\endgroup\$

2 Answers 2

9
\$\begingroup\$

It is becoming really good, but there are still a few things that can be done:

  • There already is a standard way to "clean" a type: std::decay. It does a little bit more than just removing the reference and the const qualification though, but it does basically what you need. Therefore, you can totally get rid of CleanType and use std::decay instead:

    using AllArgs = std::tuple<typename std::decay<Args>::type...>;
    

    And since you are using C++14 (I assume this because of std::integer_sequence), you can even use std::decay_t instead:

    using AllArgs = std::tuple<std::decay_t<Args>...>;
    
  • N3887 also made its way to the C++14 standard. Therefore, if you actually use C++14, you will be able to use the alias template std::tuple_element_t instead of the metafunction std::tuple_element:

    ArgumentTupple arguments(row.getValue1<std::tuple_element_t<S, ArgumentTupple>>()...);
    
  • This line looks kind of wrong:

    typedef typename std::make_integer_sequence<int, size>      Sequence;
    

    When I tried to compile your example, it didn't compile at first because of the typename in this line. std::make_integer_sequence is an alias template, there shouldn't be a typename before it.

  • You sometimes use ResultRowSet and use ResultSetRow at some other places. It's probably a typo or something like that :p

  • ArgumentTupple also feels like a typo. It should probably be ArgumentTuple.

  • std::integer_sequence does not contain anything. Therefore, there is no need to pass is by const&, you can simply pass it by value.

\$\endgroup\$
5
  • \$\begingroup\$ What is the advantage of doing using X = Y; over typedef Y X;? \$\endgroup\$ Apr 6, 2014 at 16:41
  • \$\begingroup\$ Sorry about ResultRowSet that was in the code I types in as an example and had not compiled. \$\endgroup\$ Apr 6, 2014 at 16:48
  • \$\begingroup\$ Fixed everything else. And the unit tests still work. So +1 \$\endgroup\$ Apr 6, 2014 at 16:48
  • \$\begingroup\$ @LokiAstari There's no advantage actually, I did it out of habit. At the very best, it allows you to always be consistent since using works for templates while typedef does not. Also, it's close to auto X = Y; so I never get confused about the position of the old and the new name :) \$\endgroup\$
    – Morwenn
    Apr 6, 2014 at 17:00
  • \$\begingroup\$ OK. Looks like another old habit I will need to break then. \$\endgroup\$ Apr 6, 2014 at 17:08
6
\$\begingroup\$

Good work!

I'm also happy to see there's now std::make_integer_sequence in C++14, I didn't know that when I said "similar stuff will be once part of STL" in your previous post!

I only have a couple of comments on top of Morwenn.

  • Your CleanType (in case you don't use std::decay) could just be

    template<typename Decorated>
    using CleanType = std::remove_const<typename std::remove_reference<Decorated>::type>;
    

    because

    • it's standard to use the name type for the resulting type rather than a custom name like BaseType
    • you don't need NoRefBase; it's cleaner to use std::remove_reference or std::remove_reference_t directly.
  • Tuple initialization

    ArgumentTupple  arguments(row.getValue1<typename std::tuple_element<S, ArgumentTupple>::type>()...);
    

    really needs the braces

    ArgumentTupple  arguments{row.getValue1<typename std::tuple_element<S, ArgumentTupple>::type>()...};
    

    because the right order (left-to-right) is only guaranteed in this case (iso 8.5.4/4). Otherwise, it's implementation defined.

\$\endgroup\$
5
  • \$\begingroup\$ I differentiate Type identifers (things that can not be objects) from other identifiers by using an initial capital letter. So <X>::type does not work for my naming convention. Though <X>::Type does. \$\endgroup\$ Apr 6, 2014 at 22:28
  • \$\begingroup\$ I think you mean: "Otherwise, it's implementation defined behaviour." \$\endgroup\$ Apr 6, 2014 at 22:30
  • \$\begingroup\$ @LokiAstari I think it is UB since evaluations of function arguments are unsequenced by 1.9/15 and no diagnostic is required. But I am not sure. \$\endgroup\$
    – iavr
    Apr 7, 2014 at 5:30
  • \$\begingroup\$ @ivar: Yes they are unsequenced. But "Undefined Behavior" has a specific meaning. In that the program is not valid. Just because the arguments are evaluated in an implementation defined order does not make the program invalid (just not very portable). Otherwise f(g(1), h(1)); would be an invalid program which obviously it is not. \$\endgroup\$ Apr 7, 2014 at 15:26
  • \$\begingroup\$ @LokiAstari Ok makes sense. I read its definition again and again, but I always have a hard time getting its meaning :-) Updated accordingly. \$\endgroup\$
    – iavr
    Apr 7, 2014 at 17:15

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.