lazy initialization with forwarding - c++

Take a "lazy" constructor that might have the following interface:
template<class T>
struct LazyConstruct {
// accept any number of arguments,
// which would later be used to construct T
template<class... U>
LazyConstruct(U&&... u) {
// store the arguments somehow
}
T& get() {
if(!data) data.reset( new T( /* unpack the arguments */ ) );
return *data;
}
private:
std::unique_ptr<T> data;
};
What would be a nice way to implement this?

Here's a little bit of a convoluted way of doing what you want. The basic idea is to have LazyConstruct store the arguments pack in a tuple, and then unpack the tuple on demand to construct T.
template<class T, class... Args>
struct LazyConstruct {
// accept any number of arguments,
// which would later be used to construct T
template<class... U>
LazyConstruct(U&&... u)
: args(std::make_tuple(std::forward<U>(u)...))
{
}
T& get() {
if(!data) data = create(std::index_sequence_for<Args...>());
return *data;
}
template<std::size_t... I>
std::unique_ptr<T> create(std::index_sequence<I...>)
{
return std::unique_ptr<T>{new T(std::get<I>(args)...)};
}
private:
std::tuple<typename std::decay<Args>::type...> args;
std::unique_ptr<T> data;
};
I'm making use of C++14's std::index_sequence, if your standard library implementation does not ship this, then there are several examples on SO (this or this) showing how it can be implemented.
Finally a helper function template to construct LazyConstruct instances
template<class T, class... Args>
LazyConstruct<T, Args...> make_LazyConstruct(Args&&... args)
{
return LazyConstruct<T, Args...>{std::forward<Args>(args)...};
}
Live demo
Another version based on Alf's answer that uses std::function so that LazyConstruct's type doesn't change based on T's constructor signature.
template<class T>
struct LazyConstruct {
template<class... Args>
LazyConstruct(Args&&... args)
: holder([this, args = std::make_tuple(std::forward<Args>(args)...)]() {
return create(std::index_sequence_for<Args...>(), std::move(args));
})
{
}
T& get() {
if(!data) data = holder();
return *data;
}
template<std::size_t... I, class Tuple>
std::unique_ptr<T> create(std::index_sequence<I...>, Tuple args)
{
return std::unique_ptr<T>{new T(std::get<I>(args)...)};
}
private:
std::function<std::unique_ptr<T>()> holder;
std::unique_ptr<T> data;
};
Live demo

I am not sure about your question, but for lazy initialization I suggest you to use something along the lines of boost::optional<T>. You can delay initialization with it and you will not make use of a pointer and heap memory.
class MyClass {
public:
void f();
};
void anotherFunc(MyClass & c);
boost::optional<MyClass> mc; //Not initialized, empty, stack memory.
mc = MyClass{};
if (mc != boost::none)
mc->f();
anotherFunc(*mc);
Documentation is here: Boost.Optional

The easiest is probably to just capture the arguments in a lambda.
template<class T>
struct LazyConstruct {
// accept any number of arguments,
// which would later be used to construct T
template<class... U>
LazyConstruct(U&&... u)
: create( [=]() -> T* { return new T(u...); } )
{}
T& get() {
if(!data) data.reset( data.reset( create() ) );
return *data;
}
private:
std::unique_ptr<T> data;
std::function<auto()->T*> create;
};
Disclaimer: Code not touched by compiler's hands.
Note: While I'm unable right now to say exactly what's wrong with the idea (it's pretty late), lazy creation doesn't smell right, somehow. I suspect premature optimization.

As per the comment before. You want to delay and capture the arguments.
EDIT: Generalized solution, should work in C++11. Warnings: not tested. apply function is left as an exercise. See here for a possible implementation:
template <class T>
struct make {
template <class...Args>
T operator()(Args &&... args) const {
return T(std::forward<Args>(args)...);
}
};
template <class T, class... Args>
struct object_builder {
object_builder(Args... && args) :
captured_args_(std::forward<Args>(args)...) {}
T operator()() const {
return apply(make<T>{},
captured_args_);
}
private:
std::tuple<Args...> captured_args_;
};
template <class T, class...Args>
object_builder<T, Args...> make_object_builder(Args &&...args) {
return object_builder<T, Args...>(std::forward<Args>(args)...);
}
int main() {
//Create builders with captured arguments
auto scary_monster_builder =
make_object_builder<Monster>(scary, "big orc");
auto easy_monster_builder = make_object_builder<Monster>(easy,
"small orc");
//Instantiate objects with the captured arguments from before
auto a_scary_monster = scary_monster_builder();
auto an_easy_monster = easy_monster_builder();
}

Related

How to identify if a method of underlying type held by a shared_ptr can be invoked with one or no parameters at compile time

Using C++ 17. I have the following:
template <typename T>
using ptr_t = std::shared_ptr<const T>;
class some_type;
class A { some_type foo() const; }
class B { some_type foo() const; }
class C { some_type foo(int) const; }
std::variant<ptr_t<A>, ptr_t<B>, ptr_t<C>>;
A variant holds shared_ptr(s) to different types. All expected to have function foo() that may be void or take a parameter. I will then have a visitor that would correctly dispatch foo, something like this (conceptually):
struct visitor
{
template <typename T>
ptr_t<some_type> operator()(const T& config) const
{
if constexpr (// determine if foo() of the underlying type of a shared_ptr can be called with int param)
return config->foo(15);
else
return config->foo();
}
is there a way to say this? I tried various ways but can't come with something that compiles. Template parameter, T, is ptr_t<A|B|C>.
std::is_invocable_v<Callable, Args...> is the way to go. Unfortunatelly, it will not compile just like that with if constexpr. It will either fail because "there is no operator()() overload", or there is no overload for operator taking Args....
I suggest you add a wrapper class for a callable and use it with a specialized alias template of std::variant instead of writing your own visitor. It will allow you to use std::visit seamlessly.
#include <type_traits>
#include <variant>
template <typename Callable>
class wrapped_callable
{
Callable c;
public:
wrapped_callable(Callable c)
: c(c)
{}
template <typename ... Args>
constexpr decltype(auto) operator()(Args &&... args) const
{
return _invoke(std::is_invocable<Callable, Args...>{}, c, std::forward<Args>(args)...);
}
private:
using _invocable = std::true_type;
using _non_invocable = std::false_type;
template <typename T, typename ... Args>
constexpr static decltype(auto) _invoke(_invocable, const T& t, Args &&... args)
{
return t(std::forward<Args>(args)...);
}
template <typename T, typename ... Args>
constexpr static decltype(auto) _invoke(_non_invocable, const T& t, Args ... args)
{
return t();
}
};
template <typename ... T>
using variant_callable = std::variant<wrapped_callable<T>...>;
struct int_callable
{
int operator()(int i) const
{
return i;
}
};
struct non_callable
{
int operator()() const
{
return 42;
}
};
#include <iostream>
int main()
{
using variant_t = variant_callable<int_callable, non_callable>;
// 23 is ignored, 42 is printed
std::visit([](const auto &callable){
std::cout << callable(23) << '\n';
}, variant_t{non_callable()});
// 23 is passed along and printed
std::visit([](const auto &callable){
std::cout << callable(23) << '\n';
}, variant_t{int_callable()});
}
Program returned: 0
42
23
https://godbolt.org/z/e6GzvW6n6
But The idea is not to have any specialization for all types in a variant as it will then require changing the visitor code every time a new type is added.
That is what template alias of std::variant<wrapped_callable<T>...> for. You just add append a new type to the list, that's it.
Take notice, that it does not depend on if constexpr. So if you manage to provide your own variant and is_invocable_v, it will work for C++14. For C++11 possibly, but some modifications regarding constexpr functions might be needed.
Of course you can implement your visitor in the same manner if you want to use std::shared_ptr istead of a callable.
But I don't see any reason to use:
visitor + smart pointer. Just use a smart pointer - it will give you runtime polymorphism in a "classic" way (via virtual inheritence)
why std::shared_ptr? Do you really need to share the ownership? Just stick with std::unique_ptr

Passing std::vector of wrapper of std::variant into variadic class, wrapping variadic method without knowing the specifics

The goal
I try to create a set of classes that removes boilerplate code for implementing extensions to a game in C++.
For that, I have a designated value class, that can hold one of the following types:
float, std::string, bool, std::vector<value>, void
For that, I would like to have a host class to which I can add one or more method instances like follows:
using namespace std::string_literals;
host h;
h.add(
method<bool, req<std::string>, req<std::string>, opt<bool>>("compare_strings"s,
[](std::string s_orig, std::string s_comp, std::optional<bool> ingore_case) -> bool {
if (ignore_case.has_value() && ignore_case.value()) {
// ... lowercase both
}
return s_orig.compare(s_comp) == 0;
}));
Note that req<T> should be a meta info that a given value is required, opt<T> a meta info that a given value is not required and may only be provided after all required parameters.
The host class now contains a method execute(std::string function, std::vector<value> values) with function and values originating from a method getting char* for method and ´char** argv+ int argcfor values. Theexecutemethod now is supposed to call the correctmethod` instances function
value host::execute(std::string function, std::vector<value> values) {
// get matching method group
std::vector<method> mthds = m_methods[function];
// get matching parameter list
for (method& mthd : mthds) {
if (mthd.can_call(mthds, values)) {
// call generic method
auto res = mthd.call_generic(values);
// pass result back to callee
// return [...]
}
}
// return error back to callee
// return [...]
}
which means that the actual method class now needs to mangle two methods properly can_call and call_generic.
The value class has corresponding template<typename T> bool is() and template<typename T> T get() methods.
What remains
I did have other attempts at this, but as those failed, I deleted them (not very smart in hindside, but needed to get the whole thing out as another person relied on the results working) and now cannot figure out another attempt then prior ... so this is what I am left with as of now:
class method_base
{
public:
template<typename T> struct in { using type = T; };
template<typename T> struct opt { using type = T; };
public:
virtual bool can_call(std::vector<sqf::value> values) = 0;
virtual sqf::value call_generic(std::vector<sqf::value> values) = 0;
};
template<typename T, typename ... TArgs>
class method : public method_base
{
func m_func;
sqf::value val
public:
using func = T(*)(TArgs...);
method(func f) : m_func(f) {}
virtual retval can_call(std::vector<sqf::value> values) override
{
}
};
Appendix
If something is unclear, confusing or you just have further questions, please do ask them. I will try my best to rephrase whatever is unclear as this will help greatly with developing further extensions in the future, possibly defining a "go to" way for how to create extensions in the community for the game in question (Arma 3 just in case somebody wondered)
I may note that this is pretty much my first deep dive into meta programming so things I present may not be possible at all. If so, I kindly would like to ask you if you may also explain why that is so and the thing I attempt is not possible.
The Solution
I do want to express my thanks to all who answered this question again. I ended up combining pretty much parts of all solutions here and pretty much learned a lot on the way. The final implementation I ended up with looks like the following:
namespace meta
{
template <typename ArgType>
struct is_optional : std::false_type {};
template <typename T>
struct is_optional<std::optional<T>> : std::true_type {};
template <typename ArgType>
inline constexpr bool is_optional_v = is_optional<ArgType>::value;
template <typename ArgType>
struct def_value { static ArgType value() { return {}; } };
template <typename ArgType>
struct get_type { using type = ArgType; };
template <typename ArgType>
struct get_type<std::optional<ArgType>> { using type = ArgType; };
}
struct method {
std::function<bool(const std::vector<value>&)> m_can_call;
std::function<value(const std::vector<value>&)> m_call;
template <typename ... Args, std::size_t... IndexSequence>
static bool can_call_impl(const std::vector<value>& values, std::index_sequence<IndexSequence...> s) {
// values max args
return values.size() <= sizeof...(Args) &&
// for every Arg, either...
(... && (
// the value provides that argument and its the correct type, or...
(IndexSequence < values.size() && sqf::is<sqf::meta::get_type<Args>::type>(values[IndexSequence])) ||
// the value does not provide that argument and the arg is an optional
(IndexSequence >= values.size() && sqf::meta::is_optional_v<Args>)
));
}
template <typename Ret, typename ... Args, std::size_t... IndexSequence>
static value call_impl(std::function<Ret(Args...)> f, const std::vector<value>& values, std::index_sequence<IndexSequence...>) {
return {
// call the function with every type in the value set,
// padding with empty std::optionals otherwise
std::invoke(f,
(IndexSequence < values.size() ? sqf::get<sqf::meta::get_type<Args>::type>(values[IndexSequence])
: sqf::meta::def_value<Args>::value())...)
};
}
public:
template <typename Ret, typename ... Args>
method(std::function<Ret(Args...)> f) :
m_can_call([](const std::vector<value>& values) -> bool
{
return can_call_impl<Args...>(values, std::index_sequence_for<Args...>{});
}),
m_call([f](const std::vector<value>& values) -> value
{
return call_impl<Ret, Args...>(f, values, std::index_sequence_for<Args...>{});
})
{
}
bool can_call(const std::vector<value>& values) const { return m_can_call(values); }
value call_generic(const std::vector<value>& values) const { return m_call(values); }
// to handle lambda
template <typename F>
method static create(F f) { return method{ std::function{f} }; }
};
Assumming a way to check current type of value (template <typename T> bool value::isA<T>()) and a way to retrieve the value (template <typename T> /*const*/T& get(/*const*/ value&))
It seems you might do:
struct method
{
template <typename Ret, typename ... Ts>
method(std::function<Ret(Ts...)> f) : method(std::index_sequence<sizeof...(Ts)>(), f)
{}
template <typename Ret, typename ... Ts, std::size_t ... Is>
method(std::index_sequence<Is...>, std::function<Ret(Ts...)> f) :
isOk([](const std::vector<value>& values) {
return ((values.size() == sizeof...(Is)) && ... && values[Is].isA<Ts>());
}),
call([f](const std::vector<value>& values){
return f(get<Ts>(values[Is])...);
})
{}
// to handle lambda
template <typename F>
static fromCallable(F f) { return method{std::function{f}}; }
std::function<bool(const std::vector<value>&)> isOk;
std::function<value(const std::vector<value>&)> call;
};
Here's a quick example including the machinery for ret<T> and opt<T>. You haven't given any information on what value is, so I'm going to assume something like:
struct value {
// using `std::monostate` instead of `void`
std::variant<float, std::string, bool, std::vector<value>, std::monostate> data;
};
(I'm assuming c++17 for this answer.)
From there, we need our metatypes and a few traits to branch off them. I implement them using partial specialisations, but there are other ways too.
// types to determine optional vs. required
template <typename T>
struct req { using type = T; };
template <typename T>
struct opt { using type = T; };
// trait to determine if it's an optional type
template <typename ArgType>
struct is_optional : std::false_type {};
template <typename T>
struct is_optional<opt<T>> : std::true_type {};
template <typename ArgType>
inline constexpr bool is_optional_v = is_optional<ArgType>::value;
// get the "real" function parameter type
template <typename ArgType>
struct real_type;
template <typename ArgType>
using real_type_t = typename real_type<ArgType>::type;
template <typename T>
struct real_type<req<T>> { using type = T; };
template <typename T>
struct real_type<opt<T>> { using type = std::optional<T>; };
Now we implement method. I'll use a similar polymorphic relationship with method_base as you do in your partial demo; I also template on the function type passed in, to allow e.g. the functions to use const references to the type instead of the type itself.
The implementation itself uses the common trick of delegating to helper functions with std::index_sequence and fold expressions to "iterate" through the variadic template args.
// base class for polymorphism
struct method_base {
virtual ~method_base() = default;
virtual bool can_call(const std::vector<value>& values) const = 0;
virtual value call_generic(const std::vector<value>& values) const = 0;
};
// provide a different method implementation for each set of args
// I also overload on
template<typename RetType, typename Fn, typename... Args>
struct method : method_base {
private:
Fn func;
static_assert(std::is_invocable_r_v<RetType, Fn, real_type_t<Args>...>,
"function must be callable with given args");
public:
// accept any function that looks sort of like what we expect;
// static assert above makes sure it's sensible
template <typename G>
method(G&& func) : func(std::forward<G>(func)) {}
template <std::size_t... Is>
bool can_call_impl(const std::vector<value>& values, std::index_sequence<Is...>) const {
// for every Arg, either...
return (... and (
// the value provides that argument and its the correct type, or...
(Is < values.size() and std::holds_alternative<typename Args::type>(values[Is].data))
// the value does not provide that argument and the arg is an optional
or (Is >= values.size() and is_optional_v<Args>)
));
}
bool can_call(const std::vector<value>& values) const override {
return can_call_impl(values, std::index_sequence_for<Args...>{});
}
template <std::size_t... Is>
value call_generic_impl(const std::vector<value>& values, std::index_sequence<Is...>) const {
return {
// call the function with every type in the value set,
// padding with empty std::optionals otherwise
std::invoke(func,
(Is < values.size() ? std::get<typename Args::type>(values[Is].data)
: real_type_t<Args>{})...)
};
}
value call_generic(const std::vector<value>& values) const override {
return call_generic_impl(values, std::index_sequence_for<Args...>{});
}
};
I'll also create a helper function to make methods:
template <typename RetType, typename... Args, typename Fn>
std::unique_ptr<method_base> make_method(Fn&& func) {
return std::make_unique<method<RetType, std::decay_t<Fn>, Args...>>(std::forward<Fn>(func));
}
Live example.
It's not perfect, but this should give you a general idea of how to do it.
Change your method to:
method< R(Args...) >
your tags seem useless. Detect optional with ... std::optional.
For storage, use std variant. Use some non-void type for void (I don't care what).
As a first pass we aim for perfect compatibility.
template<class...Args>
struct check_signature {
bool operator()( std::span<value const> values ) const {
if (sizeof...(Args) != values.size()) return false;
std::size_t i=0;
return (std::holds_alternative<Args>(values[i++])&&...);
}
};
this can be stored in a std::function<bool(std::span<value const>)> or just called in your class impementation.
Similar code can store the callable.
template<class F, class R, class...Args>
struct execute {
F f;
template<std::size_t...Is>
R operator()( std::index_sequence<Is...>, std::span<value const> values ) const {
if (sizeof...(Args) != values.size()) return false;
return f( std::get<Args>(values[Is])... );
}
R operator()( std::span<value const> values ) const {
return (*this)( std::make_index_sequence<sizeof...(Args)>{}, values );
}
};
some work may have to be done for the fake void.
Your method is now a aggregate.
struct method {
std::function<bool(std::span<value const>)> can_call;
std::function<value(std::span<value const>)> execute;
};
if you want it to be. The two template objects above can be stored in these two std functions.
There are probably tpyos, I just wrote this on my phone and have not tested it.
Extending this to cover optional args is a little bit of work. But nothing hard.
In both cases, you'll write a helper function that says if the argument is compatible or generates the value based on if you are past the end of the incoming vector.
Ie, std::get<Args>(values[Is])... becomes getArgFrom<Is, Args>{}(values)..., and we specialize for std optional producing nullopt if Is>values.size().

variadic template arguments with default values

I have a templated struct that has a method that (along with other parameters) accepts those templated parameters.
template<class... Types>
struct Observer
{
void subscribe(const std::string& id, Types... args)
{
// store id somehow
data = std::make_tuple(args...);
}
std::tuple<Types...> data;
};
I want to make all the templated arguments optional. So that it looks like this:
Observer<float, int> observer;
observer.subscribe("id1", 3.14, 4);
observer.subscribe("id2", 0.707); // this doesn't work!
observer.subscribe("id3"); // this doesn't work!
As far as I know there is not straight forward way? But maybe someone know a workaround or a trick.
Ideally, I'd like to provide my own defaults. Maybe like this:
enum class SomeEnum { Val1, Val2 };
Observer<float, SomeEnum, 0.f, SomeEnum::Val1> observer;
observer.subscribe("id1", 3.14);
Here is LIVE EXAMPLE.
In C++17 you can simply do something like:
template<class... Types>
struct Observer
{
static constexpr std::tuple<Types...> defaults{42, 24, 99};
template<class... Args>
void subscribe(Args... args)
{
if constexpr (sizeof...(Types) > sizeof...(Args)) {
subscribe(args..., std::get<sizeof...(Args)>(defaults));
} else {
// whatever you need with `args...`
}
}
};
Here I am simply picking them from Observer::defaults, but feel free to compute them however you want.
For C++14 and below, you will need to emulate the if constexpr. See e.g. Constexpr if alternative for alternatives.
Boost.Mp11 for the win:
template <typename... Ts>
void subscribe(const std::string& id, Ts const&... args)
{
static_assert(sizeof...(Ts) <= sizeof...(Types));
using Rest = mp_drop_c<std::tuple<Types...>, sizeof...(Ts)>;
data = std::tuple_cat(std::make_tuple(args...), Rest());
}
The assumes that value initialization of the trailing arguments is fine. If it's not, you'll have to figure out what to do with Us.
Works nicer if you actually make the optional more explicit:
template<class... Types>
struct Observer
{
using Data = std::tuple<std::optional<Types>...>;
template <typename... Ts>
void subscribe(const std::string& id, Ts const&... args)
{
static_assert(sizeof...(Ts) <= sizeof...(Types));
using Rest = mp_drop_c<Data, sizeof...(Ts)>;
data = std::tuple_cat(std::make_tuple(args...), Rest());
}
Data data;
};

how to forward the types of tuple to specialize other template?

currently I'm working on a dynamic container structure, which represents one pod value or has vector of pointers with same container type. The container has an interface optional<T> expect_value<T>() 。 For pod types the implemention is simple. For the non pod value, I would call expect_value<tuple<args...>>(), the args would be tuple as well. But when implement this function, I come across a trouble: how to redirect a.expect_value<tuple<args...>>() to a.expect_value_tuple<args...>>(). For example, the call to a.expect_value<tuple<int,int>() would return the result of a.expect_value_tuple<int, int>(). Because the argument is empty, I cant use the type deduce of unpacked arguments. Then the whole project just cant progress any more. Any ideas? Below is the minimal example for my intention.
#include <tuple>
#include <vector>
#include <optional>
#include <functional>
using namespace std;
template<typename T>
struct is_tuple_impl : std::false_type {};
template<typename... Ts>
struct is_tuple_impl<std::tuple<Ts...>> : std::true_type {};
template<typename T>
struct is_tuple : is_tuple_impl<std::decay_t<T>> {};
class my_container;
template<typename... args, size_t... arg_idx>
optional<tuple<args>...> get_tuple_value_from_vector(const vector<my_container*>& v_list, std::index_sequence<arg_idx...>)
{
auto temp_result = make_tuple((*v_list[arg_idx]).expect_value<arg>()...);
if(!(get<arg_idx>(temp_result) &&...))
{
return nullopt;
}
return make_tuple(get<arg_idx>(temp_result).value()...);
}
class my_container
{
public:
int value_type; // 1 for v_int 2 for v_list 0 empty
union
{
int v_int;
};
vector<my_container*> v_list;
template<typename T>
optional<T> expect_simple_value();
template<typename... args>
optional<tuple<args...>> expect_tuple_value();
template<typename T>
optional<T> expect_value();
};
template <typename T>
optional<T> my_container::expect_simple_value()
{
return nullopt;
}
template <>
optional<int> my_container::expect_simple_value()
{
if(value_type == 1)
{
return v_int;
}
return nullopt;
}
template<typename... args>
optional<tuple<args...>> my_container::expect_tuple_value()
{
if(v_list.size() == 0)
{
return nullopt;
}
for(const auto i: v_list)
{
if(!i)
{
return nullopt;
}
}
auto the_tuple_size = sizeof...(args);
if(v_list.size() != the_tuple_size)
{
return nullopt;
}
return get_tuple_value_from_vector<args...>(v_list, index_sequence_for<args...>{});
}
template <typename T>
optional<T> my_container::expect_value()
{
if(is_tuple<T>::value)
{
return expect_tuple_value<T>();
}
else
{
return expect_simple_value<T>();
}
}
int main()
{
my_container test_value;
test_value.value_type = 1;
test_value.v_int = 1;
auto result = test_value.expect_value<tuple<int, int>>();
if(result)
{
return 0;
}
else
{
return 1;
}
}
the heart of the problem is the line return expect_tuple_value<T>(); When logic goes there, the T should be tuple<args...>, but what I want is return return expect_tuple_value<args...>().
What about using template argument deduction and overload resolution through partial ordering of function template:
class my_container
{
public:
template<class T> optional<T> expect_value_simple();
template<class...Args> optional<tuple<Args...>> expect_value_tuple();
private:
template<class T> struct deduce_type{};
template<typename T>
auto expect_value_dispatching(deduce_type<T>){
return expect_value_simple<T>();
}
template<typename...Args>
auto expect_value_dispatching(deduce_type<tuple<Args...>>){
return expect_value_tuple<Args...>();
}
public:
template<typename T>
auto expect_value(){
return expect_value_dispatching(deduce_type<T>{});
}
};
(Demo)
The if before the line in question should be a constexpr if.
Unpacking of types is annoying to do without using a class helper. I can do it with some fancy c++14 lambda action tho.
template<class T>
struct tag_t{using type=T;};
template<class Tag>
using type=typename Tag::type;
template<class Tuple>
struct unpack_tuple;
template<class...Ts>
struct unpack_tuple<std::tuple<Ts...>> {
template<class F>
decltype(auto) operator()(F&& f)const {
return std::forward<F>(f)( tag_t<Ts>{}... );
}
};
#define TYPE_FROM(...) \
type< std::decay_t<decltype(__VA_ARGS__)> >
then we get
if constexpr(is_tuple<T>::value)
{
return unpack_tuple<T>{}([&](auto...tags){
return expect_tuple_value<TYPE_FROM(tags)...>();
});
}
else
{
return expect_simple_value<T>();
}
and done.
The core issue here is that you need to do argument deduction at least once to go back from a type to its variadic parameters. To do that, you must pass some instance of such a variadically-templated type to a function - but it does not have to be the original one.
Yakk's solution does this via a variadic lambda that is passed instances of tag-types (one per tuple type). The advantage here is that you can use a lambda instead of an explicit intermediary function every time.
Oliv's solution uses a monostate type which we can instantiate and pass to a function for type deduction. It's much cleaner but requires such an intermediary function for every use case.
Here is a (more or less theoretical) version combining both, using templated variadic lambdas (C++20, and they apparently don't even have clang support as of now):
template<class... Args>
struct MonostateTuple
{};
template<class... Args>
auto tupleToMonostate(std::tuple<Args...>)
{
return MonostateTuple<Args...>{};
}
template<class T, class F>
auto unpack_tuple(F&& f)
{
using MT = decltype(tupleToMonostate(std::declval<T>()));
return std::forward<F>(f)(MT{});
}
/// User code
template<class Tuple>
auto foo()
{
return unpack_tuple<Tuple>([&] <typename... Args> (MonostateTuple<Args...>) {
return expect_tuple_value<Args...>();
});
}
It's a bit more ugly in the lambda signature (not to mention the lack of compiler support again) but theoretically combines both advantages.

Variadic members in non-template class

I'm trying to write a class Invocation which has a templated constructor:
template<typename F>
class Invocation {
public:
template<typename... Args>
Invocation(F&& f, Args&&... args)
{ /* store f and args somewhere for later use */ }
...
};
Normally I would parameterize the Invocation class itself with both F and Args..., but in this case I need a uniform type for a given F, so I'm trying to find a way to store args... of any types inside a Invocation<F>, and to incur as little performance hit as possible. (This might not be the best design, but it can be an interesting exercise.)
One thought is to use virtual functions:
template<typename F>
class ArgsBase {
public:
// discard return value
virtual void invoke(F&& f) = 0;
};
template<typename F, typename... Ts>
class Args : public ArgsBase<F> {
public:
Args(Ts&&... args) : args_(std::forward<Ts>(args)...) {}
void invoke(F&& f) override
{
/* somehow call f with args_ (something like std::apply) */
...
}
private:
std::tuple<Ts&&...> args_;
};
And then in the Invocation<F> class, we can for example have an std::unique_ptr<ArgsBase<F>> member, which points to an Args<F, Ts...> object created in the Invocation<F> ctor. And we can call its invoke virtual method when needed.
This is just one random idea I came up with. Is there any other way to achieve this? Ideally without the overhead of virtual functions or anything like that?
UPDATE: Thanks to the comments/answers that suggest using std::function or lambdas. I should've made it clear that I'm actually interested in a more general case, i.e., the variadic stuff might not be arguments to a callable. It can be just anything that I want to store in a class whose type is not parameterized by the types of these stuff.
As mentioned in comment, I wouldn't worry about storing arguments by value. The compiler's copy-elision can be generous.
Particularly if you offer the class an r-value invoke:
#include <tuple>
template<typename F>
class ArgsBase {
public:
// discard return value
virtual void invoke(F&& f) const & = 0;
virtual void invoke(F&& f) && = 0;
};
template<typename F, class... FunctionArgs>
class Args : public ArgsBase<F> {
public:
template<class...Ts>
Args(Ts&&... args) : args_(std::forward<Ts>(args)...) {}
template<std::size_t...Is, class Tuple>
static void invoke_impl(F& f, std::index_sequence<Is...>, Tuple&& t)
{
f(std::get<Is>(std::forward<Tuple>(t))...);
}
void invoke(F&& f) const & override
{
invoke_impl(f,
std::make_index_sequence<std::tuple_size<tuple_type>::value>(),
args_);
/* somehow call f with args_ (something like std::apply) */
}
void invoke(F&& f) && override
{
invoke_impl(f,
std::make_index_sequence<std::tuple_size<tuple_type>::value>(),
std::move(args_));
/* somehow call f with args_ (something like std::apply) */
}
private:
using tuple_type = std::tuple<FunctionArgs...>;
tuple_type args_;
};
template<class Callable, class...MyArgs>
auto later(MyArgs&&...args) {
return Args<Callable, std::decay_t<MyArgs>...>(std::forward<MyArgs>(args)...);
}
void foo(const std::string&, std::string)
{
}
int main()
{
auto l = later<decltype(&foo)>(std::string("hello"), std::string("world"));
l.invoke(foo);
std::move(l).invoke(foo);
}
If you're trying to save a function call with its parameters for later invocation, you could use lambdas packaged in std::function objects:
template<typename F, typename ... Args>
std::function<void()> createInvocation(F f, const Args& ... args)
{
return [f,args...]() { f(args...); };
}
Then you could use it like this:
void myFunc(int a, int b)
{
std::cout << "Invoked: " << a + b << std::endl;
}
int main() {
auto invocation = createInvocation(myFunc, 1, 2);
invocation();
return 0;
}
UPDATE: If you wanted to create a generic non-templated container type, you can wrap a tuple into a type that itself derives from a non-templated type. The main problem then is accessing the underlying data. This can be solved by creating a static function dispatch table that for a given tuple type, redirects queries so that std::get, which requires a compile-time constant index template parameter, can instead be invoked with a dynamically provided function parameter. Here is an implementation that achieves this:
class GenericTupleContainer
{
public:
virtual const void* getItemAtIndex(size_t index) = 0;
};
template<typename ... T>
class TupleContainer : public GenericTupleContainer
{
public:
TupleContainer(T&& ... args)
: data(std::forward<T>(args)...)
{}
const void* getItemAtIndex(size_t index) override
{
if(index >= sizeof...(T))
throw std::runtime_error("Invalid index");
return dispatchTable[index](data);
}
private:
template<size_t index>
static const void* getItemAtIdx(const std::tuple<T...>& data)
{
return &std::get<index>(data);
}
using GetterFn = const void*(*)(const std::tuple<T...>&);
static GetterFn* initDispatchTable()
{
static GetterFn dispatchTable[sizeof...(T)];
populateDispatchTable<sizeof...(T)>(dispatchTable, std::integral_constant<bool, sizeof...(T) == 0>());
return dispatchTable;
}
static GetterFn* dispatchTable;
template<size_t idx>
static void populateDispatchTable(GetterFn* table, std::false_type);
template<size_t idx>
static void populateDispatchTable(GetterFn* table, std::true_type)
{
//terminating call - do nothing
}
std::tuple<T...> data;
};
template<typename ... T>
typename TupleContainer<T...>::GetterFn* TupleContainer<T...>::dispatchTable = TupleContainer<T...>::initDispatchTable();
template<typename ... T>
template<size_t idx>
void TupleContainer<T...>::populateDispatchTable(GetterFn* table, std::false_type)
{
table[idx-1] = &TupleContainer<T...>::template getItemAtIdx<idx-1>;
populateDispatchTable<idx-1>(table, std::integral_constant<bool, idx-1 == 0>() );
}
template<typename ... T>
auto createTupleContainer(T&& ... args)
{
return new TupleContainer<T...>(std::forward<T>(args)...);
}
Then you can use the above as follows:
int main() {
GenericTupleContainer* data = createTupleContainer(1, 2.0, "Hello");
std::cout << *(static_cast<const int*>(data->getItemAtIndex(0))) << std::endl;
std::cout << *(static_cast<const double*>(data->getItemAtIndex(1))) << std::endl;
std::cout << (static_cast<const char*>(data->getItemAtIndex(2))) << std::endl;
return 0;
}
As you can see from the above usage, you've achieved the aim of wrapping an arbitrary templated tuple into a non-templated type, in such a way that you can access the component members with a normal (function) index parameter instead of a template one. Now the return type of such a getter has to be universal, so I've chosen to use void* here, which is not ideal. But you can develop this idea to make this container give more useful information about the types of its data tuple members. Also, note that this does use a virtual function. With some further work you can get rid of this as well, although you won't be able to get rid of at least one function pointer lookup (i.e. the lookup in the dispatch table) - this is the price paid for gaining the flexibility of being able to use a runtime value to index into the tuple.