I have the following (incomplete, not-working) definition:
template<typename T, std::function<Args(Context&)>... Funcs>
struct constructor
{
T construct(Context& ctx)
{
return T(Funcs(ctx)...);
}
};
What I want is a templated class - the first argument is the constructed type and all following are functions to be called, the user templates with std::functions that are then called to produce the values for the constructor of type T.
I don't see a possibility to make this code work, let beside capturing the return types of the functions. I want the user to use it like this:
std::function<int(Context&)> ind = [](Context&) {return 2;};
Constructor<int, ind> c;
// c.construct(...) returns 2 by calling the constructor int(int) with argument
// ind(ctx) - which returns 2.
This might be roughly what you're looking for. Keep in mind that a std::function can't be a template parameter.
template <typename R> using Generator = std::function<R (Context&)>;
template <typename T, typename Generators, std::size_t... Is>
T constructImpl(Context& ctx, const Generators& generators,
std::index_sequence<Is...>) {
return T(std::get<Is>(generators)(ctx)...);
}
template <typename T, typename... Args>
class Constructor {
std::tuple<Generator<Args>...> generators;
public:
Constructor(Generator<Args>... generators)
: generators(std::move(generators)...)
{}
T construct(Context& ctx) {
return constructImpl<T>(ctx, generators,
std::index_sequence_for<Args...>());
}
};
Usage:
Constructor<int, int> c([](Context&) { return 2; });
int i = c.construct(context);
assert(i == 2);
Types cannot depend on run time data.
Calling std::function<X(Y)> requires run time data. So your type cannot depend on std::function<X(Y)>, so the type cannot be used as a template parameter.
Now, it can depend on a pointer to a global object: that is interestingly enough not run time state as far as C++ is concerned.
As such, your design is fundamentally flawed.
If you want a function such that it returns 2, this works:
template<class...ignored>
struct Constructor {
template<class... also_ignored>
Constructor(also_ignored&&...) {}
template<class... also_ignored>
int construct(also_ignored&&...) { return 2; }
};
this will pass the unit tests described in your OP, with the exception that you cannot pass ind to Constructor as it is not legal. However, dropping it from the type signature doesn't matter.
If you want more power, we could do this:
template<class T, class... Functors>
struct Constructor {
T construct( Context& ctx ) {
return T( Functors{}( ctx )... );
}
};
in this case you need stateless function objects:
struct ind { int operator()(Context&)const{return 2;} };
much like how std::map requires stateless comparison objects.
If your function objects require state, then you need to store a copy of them for Constructor to access (possibly within Constructor), and you might need the tuple and indexes trick to store them. ("indexes trick" is a helpful google)
I think your Construct can just be a function:
template <typename T, typename... Funcs>
T construct(Context& ctx, Funcs... funcs) {
return T(funcs(ctx)...);
}
Whose usage could be in your example could be:
int x = construct<int>(ctx, [](Context& ) { return 2; });
Related
I want to store passed data via constexpr constructor of a struct, and store the data in a std::tuple, to perform various TMP / compile time operations.
Implementation
template <typename... _Ts>
struct myInitializer {
std::tuple<_Ts...> init_data;
constexpr myInitializer(_Ts&&... _Vs)
: init_data{ std::tuple(std::forward<_Ts>(_Vs)...) }
{}
};
Stored data uses a lightweight strong type struct, generated via lvalue and rvalue helper overload:
template <typename T, typename... Ts>
struct data_of_t {
using type = T;
using data_t = std::tuple<Ts...>;
data_t data;
constexpr data_of_t(Ts&&... _vs)
: data(std::forward<Ts>(_vs)...)
{}
};
template<typename T, typename... Ts>
constexpr auto data_of(Ts&&... _vs) {
return data_of_t<T, Ts...>(std::forward<Ts>(_vs)...);
};
template<typename T, typename... Ts>
constexpr auto data_of(Ts&... _vs) {
return data_of_t<T, Ts...>(std::forward<Ts>(_vs)...);
};
It's implemented like
template <typename T = int>
class test {
public:
static constexpr auto func(int p0=0, int p1=1, int p2=3) noexcept {
return data_of <test<T>>
(data_of<test<T>>(p0, p1));
}
};
int main() {
constexpr // fails to run constexpr // works without
auto init = myInitializer (
test<int>::func()
,test<int>::func(3)
,test<int>::func(4,5)
);
std::apply([&](auto&&... args) {
//std::cout << __PRETTY_FUNCTION__ << std::endl;
auto merged_tuple = std::tuple_cat(std::forward<decltype(args.data)>(args.data)...);
}
, init.init_data);
}
Getting to the point
std::tuple_cat fails if myInitializer instance is constexpr.
std::apply([&](auto&&... args) {
auto merged_tuple = std::tuple_cat(std::forward<decltype(args.data)>(args.data)...);
It appears to be related to the const qualifier added via constexpr.
How can this be fixed?
See full example at https://godbolt.org/z/j5xdT39aE
This:
auto merged_tuple = std::tuple_cat(std::forward<decltype(args.data)>(args.data)...);
is not the right way to forward data. decltype(args.data) is going to give you the type of that data member - which is not a function of either the const-ness or value category of args. Let's take a simpler example:
void f(auto&& arg) {
g(std::forward<decltype(arg.data)>(arg.data));
}
struct C { int data; };
C c1{1};
const C c2{2};
f(c1);
f(c2);
f(C{3});
So here I have three calls to f (which call f<C&>, f<const C&>, and f<C>, respectively). In all three cases, decltype(arg.data) is... just int. That's what the type of C::data is. But that's not how it needs to be forwarded (it won't compile for c2 because we're trying to cast away const-ness -- as in your example -- and it'll erroneously move out of c1).
What you want is to forward arg, separately, and then access data:
void f(auto&& arg) {
g(std::forward<decltype(arg)>(arg).data);
}
Now, decltype(arg) actually varies from instantiation to instantiation, which is a good indicator that we're doing something sensible.
In addition of the forwarding problem denoted by Barry, there's a different reason why you cannot have constexpr on init. This is because you contain a reference to a temporary inside data_of_t.
You see, you are containing a type obtained from overload resolution from a forwarding reference:
template<typename T, typename... Ts>
constexpr auto data_of(Ts&&... _vs) {
return data_of_t<T, Ts...>(std::forward<Ts>(_vs)...);
};
The Ts... in this case could be something like int, float const&, double&. You send those reference type and then you contain them inside of the std::tuple in data_of_t.
Those temporaries are local variables from the test function:
template <typename T = int>
class test {
public:
static constexpr auto func(int p0=0, int p1=1, int p2=3) noexcept {
return data_of <test<T>>
(data_of<test<T>>(p0, p1));
}
};
The problem here is that p0, p1, p2 are all local variable. You send them in test_of_t which will contain references to them, and you return the object containing all those reference to the local variable. This is maybe the cause of the MSVC crash. Compiler are required to provide diagnostic for any undefined behaviour in constexpr context. This crash is 100% a compiler bug and you should report it.
So how do you fix that?
Simply don't contain references by changing data_of:
template<typename T, typename... Ts>
constexpr auto data_of(Ts&&... _vs) {
return data_of_t<T, std::decay_t<Ts>...>(std::forward<Ts>(_vs)...);
};
This will decay the type thus removing the references and decay any reference to C array to pointers.
Then, you have to change your constructor. You call std::forward in there but it's no forwarding occurring if you decay in the template arguments.
template<typename... Vs> requires((std::same_as<std::decay_t<Vs>, Ts>) && ...)
constexpr data_of_t(Vs... _vs)
: data(std::forward<Vs>(_vs)...)
{}
This will add proper forwarding and also constrain it properly so it always do as data_of intended.
Just doing those change will remove UB from the code, but also change it a bit. The type data_of_t will always contain values, and won't contain references. If you want to send a reference, you will need something like std::ref, just like std::bind have to use to defer parameters.
You will still need to use std::forward<decltype(arg)>(arg).data for proper forwarding as #Barry stated
I am trying to solve this problem in C++ TMP where in i need to convert one parameter pack types into another, and then convert back the types and also values. The conversion back part is based on a boolean criteria that whether an arg in Args... was transformed or not in the first place.
Basically, i have a pack(Args...). First, i transform this (for each args[i], call a transform function). It works like this:
For each arg in Args..., just create same type in transformed_args... unless it is one of following, in that case do following conversions:
Type In Args...
Type In transformed_Args...
SomeClass
shared_ptr to SomeClass
std::vector of SomeClass
std::vector of shared_ptr to SomeClass
everything else remains the same for ex:
int remains int
std::string remains std::string
I achieve this by template specialization, of course
For the next part, i take transformed_args..., publish a class and a functor. I receive call back on this functor from(C++generated Python using Pybind, not important though). Relevant bits of that class look like this...
template<typename C, typename...transformed_args..., typename... Args>
class SomeTemplateClass
{
MethodWrapper<C,void, Args...> func;
//.....
void operator()(transformed_args... targs)
{
//....
(*func.wrapped_method_inside)(transform_back_magic(targs)...) // this is want i want to achieve.
//transform_back_magic(targs)... is a plaeholder for code that checks if type of args[i]... != type of targs[i]... and then calls a tranform_back specialization on it else just return args[i].val
}
}
targs are in transformed_args... format, but underlying C++ function they are aimed for expects Args...
template<typename... Args, typename... transformed_args, ........whatever else is needed>
transform_back_magic(....)
{
if(Args[i].type != transformed_args[i].types)
tranform_back(targs[i]...);
}
the tranform_back function template logic is specialized for different cases and all logic is in place. But how to invoke that based on this boolean criteria is hitting my TMP knowledge limits. I just got started not many weeks ago.
Here i am listing down what i have created so far.
First of all this is what i need in pseudo code
template<typename C, typename... transformed_args, typename... Args>
class SomeTemplateClass
{
MethodWrapper<C,void, Args...> func;
void operator(transformed_args... targs)
{
**//In pseudo code, this is what i need**
Args... params = CreateArgsInstanceFromTransformedArgs(targs);
(*func.wrapped_method_inside)(params...);
}
}
In my attempt to implement this, so far I have decided on creating a tuple<Args...> object by copying data from targs(with conversions where ever required)
void operator(transformed_args... targs)
{
//....
auto mytup = call1(std::tuple<args...>(), std::make_index_sequence<sizeof...(Args)>,
std::make_tuple(targs...), targs...);
// mytup can be std::tuple<Args...>(transform_back(1st_targs), transform_back(2nd_targs)....). Once available i can write some more logic to extract Args... from this tuple and pass to(*func.wrapped_method_inside)(....)
(*func.wrapped_method_inside)(ArgsExtractorFromTuple(mytup)); // this part is not implemented yet, but i think it should be possible. This is not my primary concern at the moment
}
//call1
template<typename... Args, typename... Targs, std::size_t... N>
auto call1(std::tuple<Args...> tupA, std::index_sequence<N>..., std::tuple<Targs...> tupT, Targs ..)
{
auto booltup = tuple_creator<0>(tupA, tupT, nullptr); // to create a tuple of bools
auto ret1 = std::make_tuple<Args...>(call2(booltup, targs, N)...); // targs and N are expanded together so that i get indirect access to see the corresponding type in Args...
return ret1;
}
// tuple_creator is a recursive function template with sole purpose to create a boolean tuple.
// such that std::get<0>(booltup) = true,
//if tuple_element_t<0,std::tuple<Args...>> and tuple_element_t<0,std::tuple<targs...>> are same types else false
template<size_t I, typename... Targs, typename... Args>
auto tuple_creator(std::tuple<Args...>tupA, std::tuple<Targs...>tupT, std::enable_if_t<I == sizeof...(targs)>*)
{
return std::make_tuple(std::is_same<std::tuple_element_t<I-1, std::tuple<Targs...>>, std::tuple_element_t<I-1, std::tuple<Args...>>>::value);
}
template<size_t I = 0, typename... Targs, typename... Args>
auto tuple_creator(std::tuple<Args...>tupA, std::tuple<Targs...>tupT, std::enable_if_t<I < sizeof...(targs)>*)
{
auto ret1 = tuple_creator<I+1>(tupA, tupT, nullptr);
if(!I)
return ret1;
auto ret2 = std::is_same<std::tuple_element_t<I-1, std::tuple<Targs...>>, std::tuple_element_t<I-1, std::tuple<Args...>>>::value;
return std::tuple_cat(ret1, std::make_tuple(ret2));
}
template<typename TT, typename Tuple>
auto call2(Tuple boolyup, TT t, std::size_t I)
{
auto ret = transform_back<std::get<I>(booltup)>(t); // error: I is not a compile time constant
return ret;
}
transform_back is a template that uses a bool template param and enable_if based specialization to decide whether transform an argument back or not
below are the transform_back specialization for std::vector. Similarly i have others for when T = Class etc and so on
template<bool sameTypes, typename T>
std::enable_if_t<(is_vector<T>::value, is_shared_ptr<typename T::value_type>::value &&
is_class<remove_cvref_t<typename T::value_type_element_type>>::value
&& sameTypes), T>
transform_back(T val) // it was never transfoemd in first place, return as is
{
return val;
}
template<bool sameTypes, typename T>
std::enable_if_t<(is_vector<T>::value, is_shared_ptr<typename T::value_type>::value
&& is_class<remove_cvref_t<typename T::value_type_element_type>>::value
&& !sameTypes),
typename std::vector<typename T::value_type::element_type>>
transform(T val)
{
std::vector<T::value_type::element_type> t;
for(int i = 0 ; i < val.size(); ++i)
{
typename T::value_type::element_type obj = *val[i];
t.push_back(obj);
}
return t;
}
Both these specialization are same and only differ on sameTypes boolean variable
This code currently errors out in call2 method while trying to using
std::get
auto ret = transform_back<std::get<I>(booltup)>(t); // error: I is not a compile time constant
How can you help?
1)What could be the work around to std::get issue here? Just cant figure out a way to fit in std::size_t as template arg here instead of function arg to make it work at compile time.
Other than this:
2)If you can suggest an alternative approach to implement from top level.
Args... params = CreateArgsInstanceFromTransformedArgs(targs);
That would be great. The path i took is not very convincing personally to me.
If I understand correctly, you might do something like:
template <typename> struct Tag{};
std::shared_ptr<SomeClass> transform_to(Tag<std::shared_ptr<SomeClass>>, const SomeClass& s)
{
return std::make_shared<SomeClass>(s);
}
std::vector<std::shared_ptr<SomeClass>> transform_to(Tag<std::vector<std::shared_ptr<SomeClass>>>, const std::vector<SomeClass>& v)
{
std::vector<std::shared_ptr<SomeClass>> res;
res.reserve(v.size());
for (const auto& s : v) {
res.emplace_back(std::make_shared<SomeClass>(s));
}
return res;
}
const SomeClass& transform_to(Tag<SomeClass>, const std::shared_ptr<SomeClass>& s)
{
return *s;
}
std::vector<SomeClass> transform_to(Tag<std::vector<SomeClass>>, const std::vector<std::shared_ptr<SomeClass>>& v)
{
std::vector<SomeClass> res;
res.reserve(v.size());
for (const auto& s : v) {
res.emplace_back(*s);
}
return res;
}
template <typename T>
const T& transform_to(Tag<T>, const T& t) { return t; } // No transformations
And then
std::function<void (Args...)> func;
template <typename ... transformed_args>
void operator () (transformed_args... targs) const
{
func(transform_to(Tag<Args>(), targs)...);
}
Just explaining the use case here to add some context. Consider these three methods in C++ each represented with the function pointer SomeTemplateClass::func:
void foo(vector<shared_ptr<SomeClass>>) // 1
// Args... = vector<shared_ptr<SomeClass>>, Targs... = vector<shared_ptr<SomeClass>>
void foo(vector<SomeClass>) // 2
// Args... = vector<SomeClass>, Targs... = vector<shared_ptr<SomeClass>>
void foo(vector<SomeClass>, vector<shared_ptr<SomeClass>>) // 3
// Args... = vector<SomeClass>, vector<shared_ptr<SomeClass>>, Targs... = vector<shared_ptr<SomeClass>>, vector<shared_ptr<SomeClass>>
One instance each of SomeTemplateClass is exposed to Python via Pybind. I do these transformations so that when foo is called from Python, any arg vector<T>(in C++) is received as vector<shared_ptr<T>> in SomeTemplateClass functor. This helps in to get handle to previously created objects T that i need.
But as you can see from 3 cases for foo, foo(vector<shared_ptr<T>>) does not need to be transformed to and subsequently not need to be transformed back. The case of 'tranform_to'is easily handled with template specialization, but while transforming back, vector<shared_ptr<T>> cant be blindly converted back to vector<T>. So (transform(targs...)) needs an additional logic to transform a particular arg (or targ) only when targ[i]::type != arg[i]::type
Building on Jarod's answer, i rather need something like this where in transform_to method for vector<shared_ptr> is further divided in two possible templates
template<bool wasOriginallyTransformed>
enable_if<!wasOriginallyTransformed, std::vector<std::shared_ptr<SomeClass>> transform_to(Tag<std::vector<SomeClass>>, const std::vector<std::shared_ptr<SomeClass>>& v)
{
return v;
}
template<bool wasOriginallyTransformed>
enable_if<!wasOriginallyTransformed, std::vector<<SomeClass>
transform_to(Tag<std::vector<SomeClass>>, const std::vector<std::shared_ptr<SomeClass>>& v)
{
std::vector<SomeClass> res;
res.reserve(v.size());
for (const auto& s : v) {
res.emplace_back(*s);
}
return res;
}
I recently came across a situation where I ended up with a large number of nested lambdas to build asynchronous computation chains.
template <typename F>
struct node : F
{
node(F&& f) : F{std::move(f)}
{
}
template <typename FThen>
auto then(FThen&& f_then)
{
return ::node{[p = std::move(*this), t = std::move(f_then)]()
{
}};
}
};
int main()
{
auto f = node{[]{ }}.then([]{ }).then([]{ });
return sizeof(f);
}
All the objects I capture in my the lambdas are empty, yet the size of the final object is greater than one: example on gcc.godbolt.org.
If I change the lambda inside node</* ... */>::then to a function object with explicit EBO, the size of the final object becomes one.
template <typename P, typename T>
struct node_lambda : P, T
{
node_lambda(P&& p, T&& t) : P{std::move(p)}, T{std::move(t)}
{
}
void operator()()
{
}
};
template <typename FThen>
auto node</* ... */>::then(FThen&& f_then)
{
return ::node{node_lambda{std::move(*this), std::move(f_then)}};
}
Live example on gcc.godbolt.org
I find this really annoying because I'm forced to either:
Write a lot of boilerplate code that is roughly equivalent to the lambda.
Pay an additional memory cost due to the fact that something like EBO doesn't apply to lambda captures.
Is there anything in the Standard that explicitly forces empty lambda captures to take additional space? If so, why?
From expr.prim.lambda.capture:
For each entity captured by copy, an unnamed non-static data member is declared in the closure type.
While the lambdas here have no capture:
auto f = node{[]{ }}.then([]{ }).then([]{ });
and hence have no unnamed non-static data members, and hence are empty, that's not what then() actually uses. It uses this:
return ::node{[p = std::move(*this), t = std::move(f_then)](){}};
that lambda captures t and p by copy, and hence has two unnamed non-static data members. Each .then() adds another member variable, even if each one is empty, hence the size of the node keeps going up.
Or in other words, the empty base optimization only applies to bases, and capture for lambdas doesn't create bases, it creates non-static data members.
The other answers have the cause, so I will not re-iterate. I will just add that I was able to turn your example into an inheritance based one without too much boilerplate. Since you do public inheritance in the OP, I opted to remove the c'tor and go for aggregate initialization.
It only required two deduction guides to make the code almost as pretty as your original scheme:
Live on Coliru
#include <utility>
#include <iostream>
struct empty {
void operator()() {}
};
template <typename P, typename T>
struct node : P, T
{
template <typename FThen>
auto then(FThen&& f_then)
{
return ::node{std::move(*this), std::forward<FThen>(f_then)};
}
void operator()() {
P::operator()();
T::operator()();
}
};
template <typename P> node(P) -> node<P, ::empty>;
template <typename P, typename T> node(P, T) -> node<P, T>;
int main()
{
auto f = node{[]{ }}.then([]{ }).then([]{ });
std::cout << sizeof(f);
}
The EBO was applied, as you can see by following the link.
BTW, since we are are moving *this, it may be worth to r-value qualify node::then. Just to avoid any nastiness.
Given the as-if rule and [expr.prim.lambda.closure]/2:
An implementation may define the closure type differently from what is
described below provided this does not alter the observable behavior
of the program other than by changing:
the size and/or alignment of the closure type,
whether the closure type is trivially copyable (Clause [class]),
whether the closure type is a standard-layout class (Clause [class]), or
whether the closure type is a POD class (Clause [class]).
I don't see anything preventing an implementation from using some kind of magic to optimize away the storage for the captured empty variable.
That said, doing so would be an ABI break, so don't hold your breath.
Allowing - or requiring - an implementation to make the type of a captured empty variable a base of the closure type, on the other hand, would be a horrendously bad idea. Consider:
struct X { };
struct Y { };
void meow(X x); // #1
void meow(Y y); // #2
void meow(std::function<void()> f); // #3
template<class T, class U>
void purr(T t, U u) {
meow([t = std::move(t), u = std::move(u)] { /* ... */ });
}
It would be insane for purr to do anything other than call #3, yet if captures can become bases then it can call #1, or #2, or be ambiguous.
As others have noted, lambdas are specified to capture as member variables not as bases. So you are out of luck.
What you could do is take a page from bind.
Suppose you have a tuple that does use empty base optimization. Then we can write a helper:
template<class Sig>
struct lambda_ebo_t;
template<class F, class...Args>
struct lambda_ebo_t<F(Args...)>:
private std::tuple<Args...>,
private F
{
decltype(auto) operator()(){
return std::apply( (F&)*this, (std::tuple<Args...>&)*this );
}
template<class...Ts>
lambda_ebo_t( F f, Ts&&...ts ):
std::tuple<Args...>( std::forward<Ts>(ts)... ),
F( std::move(f) )
{}
};
template<class F, class...Args>
lambda_ebo_t<F, std::decay_t<Args>...>
lambda_ebo( F f, Args&&...args ) {
return {std::move(f), std::forward<Args>(args)...};
}
That is a bunch of boilerplate, and incomplete (reference capture may not work right even if you use std::ref), but it gives us:
template <typename FThen>
auto then(FThen&& f_then)
{
return ::node{lambda_ebo([](auto&& p, auto&& t)
{
}, std::move(*this), std::move(f_then))};
}
where we store the data outside the lambda and pass it in as arguments to the lambda. The storage uses EBO.
No need to write a custom EBO class for each lambda, just a few hoops to jump through when you need a lambda with EBO enabled.
This is one without using the tuple, but it doesn't support fundamantal types like int or other things you cannot derive from:
template<class Sig>
struct lambda_ebo_t;
template<class F, class...Args>
struct lambda_ebo_t<F(Args...)>:
private Args...,
// private std::tuple<Args...>,
private F
{
decltype(auto) operator()(){
//return std::apply( (F&)*this, (std::tuple<Args...>&)*this );
return ((F&)(*this))((Args&)*this...);
}
template<class...Ts>
lambda_ebo_t( F f, Ts&&...ts ):
Args(std::forward<Ts>(ts))...,
F( std::move(f) )
{}
};
template<class F, class...Args>
lambda_ebo_t<F(std::decay_t<Args>...)>
lambda_ebo( F f, Args&&...args ) {
return {std::move(f), std::forward<Args>(args)...};
}
Live example, with this test code:
auto test = lambda_ebo( [](auto&&...args){std::cout << sizeof...(args) << "\n";}, []{} , []{}, []{}, []{}, []{}, []{}, []{}, []{}); //
std::cout << "bytes:" << sizeof(test) << "\n";
std::cout << "args:";
test();
sizeof(test) is 1, and it "captures" 8 arguments.
Empty base optimization works for me in the following case
#include <utility>
template <typename F>
class Something : public F {
public:
Something(F&& f_in) : F{std::move(f_in)} {}
};
int main() {
auto something = Something{[]{}};
static_assert(sizeof(decltype(something)) == 1);
}
Live example here https://wandbox.org/permlink/J4m4epDUs19kp5CH
My guess is that the reason it's not working in your case is that the lambda you use in the then() method is not actually empty, it has member variables - the ones listed in your capture. So there is no real empty base there.
If you change the last line of your code to just return node{[]{}} then it works. The lambdas used by .then() do not materialize as "empty" classes.
Whereas in the explicit struct case, it has no member variables per se, only classes it derives from, hence empty base optimization can work there.
In a code like this:
template<class...> struct pack{};
template<class, class = int>
struct call_t
{
template<class... args_t>
static int apply(args_t&&...)
{ return 0; }
};
template<class... args_t>
struct call_t<pack<args_t...>, // (1)
std::decay_t<decltype(convert(declval<args_t>()...))> >
{
template<class... params_t> // (2)
static int apply(params_t&&... args)
{ return convert(std::forward<params_t>(args)...); }
};
template<class... args_t>
auto test(args_t&&... args) // (3)
{
return call_t<pack<args_t...> >::
apply(std::forward<args_t>(args)...);
}
This function sends a pack of params to a function or another according to if a function convert exists and can be called with the passed params, keeping intact (I guess) their exact passed types, and when its return type is int, irrespective of reference or const qualifiers.
I have three doubts with that piece of code.
(1) Is declval return type still a universal reference? For instance, declval<T>(), with T = int&, will its return type be int&& (a true r-value reference), or int & && and be deduced again as int& following the usual rules of universal references when passing to another call? I supposed that it doesn't (as pointed out by #101010), but I don't know in that case how to make a perfect overloading test.
(2) Do I need to re-specify the variadic template to use the universal reference deduction rules, or since the correct types have been already deduced in (3), they keep their deduced types intact?
Or can I just write
template<class... args_t>
struct call_t<pack<args_t...>, // (1)
std::decay_t<decltype(convert(declval<args_t>()...))> >
{
// (2)
static int apply(args_t... args)
{ return convert(args...); }
};
?
The call_t template class is an implementation detail, so, it will be instantiate only inside test.
Those two cases are not equivalent. This example:
template<class... args_t>
struct call_t<pack<args_t...>, // (1)
std::decay_t<decltype(convert(declval<args_t>()...))> >
{
// (2)
static int apply(args_t... args)
{ return convert(args...); }
};
does not forward anything. The parameter pack args... are lvalues, because they have names. That is very different than this code:
template<class... params_t> // (2)
static int apply(params_t&&... args)
{ return convert(std::forward<params_t>(args)...); }
in which args... is forwarded. The resulting behavior is that the new example could be slower than the old example (doing copies instead of moves) or could simply surprisingly fail to compile. Consider convert(std::unique_ptr<int> ). That is invocable with args_t = {std::unique_ptr<int>}, but the internal apply() would fail because you'd be trying to copy the unique_ptr.
You'd need to do:
static int apply(args_t... args)
{ return convert(std::forward<args_t>(args)...); }
I would like to create template class which could store function pointer and arguments for a this function so the function can be later invoked with this arguments.
I would like to write this universally and not to depend on argument types or number.
Here is a scatch of the idea with the use of variadic templates of c++11:
template<class T, typename... Params>
class LazyEvaluation {
private:
// Function to be invoked later
T (*f)(Params...);
// Params for function f
Params... storedParams; // This line is not compilable!
bool evaluated;
T result;
public:
// Constructor remembers function pointer and parameters
LazyEvaluation(T (*f)(Params...),Params... params)
: f(f),
storedParams(params) //this line also cannot be compiled
{}
// Method which can be called later to evaluate stored function with stored arguments
operator T&() {
// if not evaluated then evaluate
if (! evaluated) {
result = f(storedParams...);
evaluated = true;
}
return result;
}
}
I would like to have at least the public interface of this class type safe if it is possible. Although getting this work at least somehow is more important.
I've managed to save the variable number of arguments somehow. But I wasn't able to pass them to the function f. I will write it to answers, but I would like you to think about your own solutions before you see my ugly not working attempt.
I am tring to compile the code above with Microsoft Visual C++ Compiler Nov 2012 CTP (v120_CTP_Nov2012), but it would be best if a compiler independent solution would exist.
Thank you
Here is how I tried to solve it:
The parametr pack can be recursivle expanded and each parametr saved. Function store is supposed to do it. It uses one (two times overloaded) helper function.
template<typename T>
void storeHelperFunction(void*& memory, T last) {
*((T*)memory) = last;
memory = (void*)((char*)memory + sizeof(T));
}
template<typename T, typename... Params>
void storeHelperFunction(void*& memory, T first, Params... rest) {
storeHelperFunction(memory, first);
storeHelperFunction(memory, rest...);
}
template<typename... Params>
void store(void* memory, Params... args) {
// Copy of pointer to memory was done when passing it to this function
storeHelperFunction(memory, args...);
}
Function store takes a pointer to memory where the varialbe number of arguments is supposed to be saved.
The pointer can point to some dynamicly allocated memory or beter to the structure which size is equal to sizeof...(Params).
Such structure which has exactly any desiared size can be constructed using template metaprogramming:
template <int N>
struct allocatorStruct {
char byte1;
allocatorStruct<N-1> next;
};
template <>
struct allocatorStruct<1> {};
I am not sure what the standart says or how the other compilers than the microsoft one compile it. But using my compiler the sizeof(allocatorStruct) is equal to N for any N which is greater or equal to 1.
Hence allocatorStruct<sizeof...(Params)> has the same size as Params.
Another way to create something which has the same size as Params is to use a type char [sizeof...(Params)]. This has the disadvantage that the compiler passes only pointer to this array when you try to pass such array as argument.
That is why it is better to use allocatorStruct<sizeof...(Params)>.
And now the main idea:
When saving the function we can cast it to: T (*)(allocatorStruct<sizeof...(Params)>).
When saving the arguments for the function we can save them to struct of the type allocatorStruct<sizeof...(Params)>.
The size of the arguments is the same. Although the function pointer lies about the type of the function the function pointed to will get its data correctly.
At least I hoped. Depending on the calling convention I expected that the passed arguments can be reordered or wrong because of the difference between left to right saving arguments and right to left passing. But it wasn't the case. Using __cdecl calling convention only first argument was passed and the other was lost. With other calling conventions the program stoped working.
I didn't spend much time debugging it and looking to data in memory(on stack). Is it at least right way to go?
Simply use a lambda expression
// Some function.
int add(int a, int b) {
return a + b;
}
auto lazyFunc = [] { return add(1, 2); };
std::cout << lazyFunc() << std::endl; // Evaluate function and output result.
If you really want to create a class that only evaluates the function once (lazily), using variadic templates, you could do something like in the following code.
I also made the class as such that you don't have to create a new instance every time the parameters change. I use a std::tuple to store the given arguments, and compare against previously given arguments. If the arguments differ, then the function will be reevaluated.
Functions are passed around and stored using a std::function wrapper so I don't have to work with raw function pointers (yuck).
#include <iostream>
#include <functional>
#include <utility>
#include <tuple>
template <typename T>
class LazyEvaluation {};
template <typename ReturnType, typename... Params>
class LazyEvaluation<ReturnType(Params...)> {
private:
std::function<ReturnType(Params...)> func_;
ReturnType result;
std::tuple<Params...> oldParams; // Contains the previous arguments.
public:
explicit LazyEvaluation(std::function<ReturnType(Params...)> func)
: func_(std::move(func)) {}
template <typename... Args>
ReturnType operator() (Args&&... args) {
auto newParams = std::make_tuple(std::forward<Args>(args)...);
// Check if new arguments.
if (newParams != oldParams) {
result = func_(std::forward<Args>(args)...);
oldParams = newParams;
std::cout << "Function evaluated" << std::endl;
}
std::cout << "Returned result" << std::endl;
return result;
}
};
int main() {
auto f = [] (int a, int b) {
return a + b;
};
// Specify function type as template parameter.
// E.g. ReturnType(Param1Type, Param2Type, ..., ParamNType)
LazyEvaluation<int(int, int)> ld(f);
std::cout << ld(1, 2) << std::endl;
std::cout << ld(1, 2) << std::endl;
std::cout << ld(3, 4) << std::endl;
}
Output:
Function evaluated
Returned result
3
Returned result
3
Function evaluated
Returned result
7
Given the standard machinery for forming variadic index packs:
template <std::size_t... I> struct index_sequence {};
template <std::size_t N, std::size_t... I>
struct make_index_sequence : public make_index_sequence<N-1, N-1, I...> {};
template <std::size_t... I>
struct make_index_sequence<0, I...> : public index_sequence<I...> {};
and to call functions with unpacked tuple arguments:
template <typename Function, typename... Types, std::size_t... I>
auto apply_(Function&& f, const std::tuple<Types...>& t, index_sequence<I...>)
-> decltype(std::forward<Function>(f)(std::get<I>(t)...)) {
return std::forward<Function>(f)(std::get<I>(t)...);
}
template <typename Function, typename... Types>
auto apply(Function&& f, const std::tuple<Types...>& t)
-> decltype(apply_(f, t, make_index_sequence<sizeof...(Types)>())) {
return apply_(f, t, make_index_sequence<sizeof...(Types)>());
}
This is fairly straightforward:
template<typename Function, typename... Params>
class LazyEvaluation {
private:
typedef decltype(std::declval<Function>()(std::declval<Params>()...)) result_type;
// Function to be invoked later
Function f;
// Params for function f
std::tuple<Params...> storedParams;
mutable bool evaluated;
union {
std::aligned_storage<sizeof(result_type)> space;
mutable result_type result;
};
// Method which can be called later to evaluate stored function with stored arguments
void evaluate() const {
// if not evaluated then evaluate
if (! evaluated) {
new (&result) result_type{apply(f, storedParams)};
evaluated = true;
}
}
public:
// Constructor remembers function pointer and parameters
LazyEvaluation(Function f, Params... params)
: f(std::move(f)),
storedParams(std::move(params)...),
evaluated(false)
{}
~LazyEvaluation() {
if (evaluated)
result.~result_type();
}
operator result_type&() {
evaluate();
return result;
}
operator const result_type& () const {
evaluate();
return result;
}
};
template <typename Function, typename... Params>
LazyEvaluation<Function, Params...>
make_lazy(Function&& f, Params&&... params) {
return {std::forward<Function>(f), std::forward<Params>(params)...};
}
I've used a union and placement new to store the result of evaluation so that it doesn't need to be a default-constructible type, and some mutable tricks so that a const LazyEvaluator can be converted as well as a non-const instance.