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;
}
};
}
2 Answers 2
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 theconst
qualification though, but it does basically what you need. Therefore, you can totally get rid ofCleanType
and usestd::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 usestd::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 metafunctionstd::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 atypename
before it.You sometimes use
ResultRowSet
and useResultSetRow
at some other places. It's probably a typo or something like that :pArgumentTupple
also feels like a typo. It should probably beArgumentTuple
.std::integer_sequence
does not contain anything. Therefore, there is no need to pass is byconst&
, you can simply pass it by value.
-
\$\begingroup\$ What is the advantage of doing
using X = Y;
overtypedef Y X;
? \$\endgroup\$Loki Astari– Loki Astari2014年04月06日 16:41:16 +00:00Commented 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\$Loki Astari– Loki Astari2014年04月06日 16:48:24 +00:00Commented Apr 6, 2014 at 16:48 -
\$\begingroup\$ Fixed everything else. And the unit tests still work. So +1 \$\endgroup\$Loki Astari– Loki Astari2014年04月06日 16:48:54 +00:00Commented 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 whiletypedef
does not. Also, it's close toauto X = Y;
so I never get confused about the position of the old and the new name :) \$\endgroup\$Morwenn– Morwenn2014年04月06日 17:00:26 +00:00Commented Apr 6, 2014 at 17:00 -
\$\begingroup\$ OK. Looks like another old habit I will need to break then. \$\endgroup\$Loki Astari– Loki Astari2014年04月06日 17:08:45 +00:00Commented Apr 6, 2014 at 17:08
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 usestd::decay
) could just betemplate<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 likeBaseType
- you don't need
NoRefBase
; it's cleaner to usestd::remove_reference
orstd::remove_reference_t
directly.
- it's standard to use the name
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.
-
\$\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\$Loki Astari– Loki Astari2014年04月06日 22:28:41 +00:00Commented Apr 6, 2014 at 22:28 -
\$\begingroup\$ I think you mean: "Otherwise, it's implementation defined behaviour." \$\endgroup\$Loki Astari– Loki Astari2014年04月06日 22:30:54 +00:00Commented 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– iavr2014年04月07日 05:30:22 +00:00Commented 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\$Loki Astari– Loki Astari2014年04月07日 15:26:09 +00:00Commented 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– iavr2014年04月07日 17:15:54 +00:00Commented Apr 7, 2014 at 17:15
Explore related questions
See similar questions with these tags.