I have a function wrapper for use over the network:
#pragma once
#include <tuple>
#include <functional>
struct ZPackage {
std::unique_ptr<int> m_dummy;
template<typename T>
T Read() {
T t = T();
return t;
}
};
class ZRpc;
template <class C, class Tuple, class F, size_t... Is>
constexpr auto invoke_tuple_impl(F f, C& c, ZRpc* rpc, Tuple t, std::index_sequence<Is...>) {
return std::invoke(f, c, rpc, std::move(std::get<Is>(t))...);
}
template <class C, class Tuple, class F>
constexpr void invoke_tuple(F f, C& c, ZRpc* rpc, Tuple t) {
invoke_tuple_impl(f, c, rpc, std::move(t),
std::make_index_sequence < std::tuple_size<Tuple>{} > {}); // last arg is for template only
}
class ZRpcMethodBase
{
public:
virtual void Invoke(ZRpc* pao, ZPackage& pkg) = 0;
};
template<class C, class...Args>
class ZRpcMethod final : public ZRpcMethodBase {
using Lambda = void(C::*)(ZRpc*, Args...);
C* object;
Lambda lambda;
template<class F>
auto Invoke_impl(ZPackage& pkg) {
return std::tuple(pkg.Read<F>());
}
// Split a param,
// Add that param from Packet into tuple
template<class F, class S, class...R>
auto Invoke_impl(ZPackage& pkg) {
auto a(Invoke_impl(pkg));
std::tuple<S, R...> b = Invoke_impl<S, R...>(pkg);
return std::tuple_cat(a, b);
}
public:
ZRpcMethod(C* object, Lambda lam) : object(object), lambda(lam) {}
void Invoke(ZRpc* rpc, ZPackage& pkg) override {
// Invoke_impl returns a tuple of types by recursion
if constexpr (sizeof...(Args))
{
auto tupl = Invoke_impl<Args...>(pkg);
invoke_tuple(lambda, object, rpc, tupl);
}
else
{
// works like ~magic~
std::invoke(lambda, object, rpc);
}
}
};
I have added in some of the types that are utilized, ZRpc, ZPackage, and an example Object.
I am struggling with getting this wrapper to work with types that have a deleted copy constructor, such as std::unique_ptr (or in this example the ZPackage which contains the std::unique_ptr. The specific error I get:
std::tuple::tuple(const std::tuple &)': attempting to reference a deleted function
#include "TestRpc.h"
class ZRpc { };
struct Object {
void ok_method(ZRpc* rpc, int i) {
}
void broken_method(ZRpc* rpc, ZPackage pkg) {
}
};
int main() {
ZRpc rpc;
Object obj;
// compiles fine
auto a = new ZRpcMethod(&obj, &Object::ok_method);
// does not compile
auto b = new ZRpcMethod(&obj, &Object::broken_method);
}
I doubt that it will make a difference, but just for reference, here are some things I have tried and commented out previously with no avail: https://pastebin.com/aHSsLzWe. I am unable to wrap my head around variadic templates and how to correctly forward.
How can I achieve perfect forwarding with move-only constructor types?
EDIT:
I changed the std::forward to move
In the Invoke() function body, since if constexpr (sizeof...(Args)) == 1 is true, this will invoke Invoke_impl<Args...>(pkg) which will return a std::tuple<ZPackage> which is move-only, so you also need to std::move it into invoke_tuple().
if constexpr (sizeof...(Args)) {
auto tupl = Invoke_impl<Args...>(pkg);
invoke_tuple(lambda, object, rpc, std::move(tupl));
} else {
// ...
}
Related
How can I store the addresses of the arguments,
and make the function use them instead of the values it was initialized with?
This is not a running code, just the goal I would like to achieve.
class Class {
private:
Function function_; // e.g. : int Sum(int a, int b) { return a+b; } ;
std::tuple<Args...> args; // a,b provided are static consts.
public:
Class(Function _function, Args... _args) :
function_ { std::forward<Function>(_function) }
args{std::make_tuple( std::forward<Args>(_args)...) }
{}
void run_fucntion()
{
// use the addresses of a,b
function_( *p_a, *p_b ... ) // How do I do that?
}
You can use std::apply to apply the tuple of reference (not pointer but it do refer to the original object)
note: not sure what p_a and p_b supposed to be, but you can add then to the tuple with std::tuple_cat
#include <tuple>
template <typename F,typename ... Args>
class Class {
private:
F function;
std::tuple<Args...> args;
public:
Class(F&& function, Args&&... _args):
function(std::forward<F>(function)),
args{std::forward<Args>(_args)...}
{}
decltype(auto) run_fucntion()
{
return std::apply(function,args);
}
};
template <typename F,typename...Args>
Class(F&&,Args&&...) -> Class<F&&,Args&&...>;
auto f(int x){
return Class([](int a,int b){return a+b;},1,x).run_fucntion();
}
https://godbolt.org/z/jnY9sod94
if this is what you want, you can even pack them at first place
template <typename F,typename ... Args>
class Class {
private:
std::function<std::invoke_result_t<F,Args...>()> function;
public:
Class(F&& f, Args&&...args)
:function([&]{return std::forward<F>(f)(std::forward<Args>(args)...);}){}
decltype(auto) run_fucntion(){
return function();
}
};
template <typename F,typename...Args>
Class(F&&,Args&&...) -> Class<F&&,Args&&...>;
I'm trying to create arguments out of a variadic template and forward it to a stored function. If the arguments are (typename... Args) I want to iterate each type and fetch an argument of that type from a storage container and then forward the arguments to a function.
I've tried different methods but always end up with that I cant store an untyped vector of arguments and I can't forward a vector as seperated arguments.
This is some pseudocode-ish of what I want to accomplish.
template <typename S, typename... Args>
void store_lambda() {
vec.push_back([this]() -> void {
ArgumentList arguments;
(get_arguments<Args>(arguments), ...);
my_function(arguments...);
});
}
template <typename T>
void get_arguments(ArgumentList& arguments) {
arguments.append(inner_storage.get<T>();)
}
void my_function(SomeStruct& s, const AnotherStruct& as) {
// do something with arguments
}
The type ArgumentList is not implemented (and probly is impossible to do) but this is the system I'm trying to create.
EDIT: More explaination
This is how my system looks atm:
struct WorkerWrapperBase {
public:
virtual ~WorkerWrapperBase() {}
}
template <typename... Args>
using exec_fn = std::function<void()>;
template <typename Job>
using job_ptr = std::unique_ptr<Job>;
template <typename J, typename R = void, typename... Args>
struct WorkerWrapper {
exec_fn<Args...> fn;
job_ptr<J> job_ptr;
WorkerWrapper(J* job, R (S::*f)(Args...) const)
: job_ptr{job_ptr<J>(job)} {
fn = [this, f]() -> R {
(job_ptr.get()->*f)(/* send arguments fetched from Args as explained above */);
};
}
};
struct CollectionOfWorkers {
std::vector<WorkerWrapperBase> workers;
template <typename Worker>
void add() {
workers.push_back(WorkerWrapper(new Worker(), &Worker::execute));
}
}
Usage would look like this:
struct TestWorker {
void execute(SomeStruct& s, const AnotherStruct& as) const {
// do something with arguments
}
}
CollectionOfWorkers.add<TestWorker>();
// and then somewhere we can loop each Worker and call their execute function
I want to create a clean API where you can create a Worker with a simple struct containing an execute function. The types of the parameters will then be used to try to get the reference of an instance of each type thats stored in a container. And then send it to the execute function. The idea was taken from this Game Engine talk
We can bind together a function and arguments to be called later very easily:
auto bind_args = [](auto&& func, auto&&... args) {
return [=]() mutable {
return func(args...);
};
};
And we can erase this type so it can be executed later easily too:
template<class Ret = void>
struct FuncInterface {
virtual ~VirtualFunc() = default;
virtual Ret operator()() = 0;
// Provided to allow concrete classes to copy themselves
virtual FuncInterface* clone() const = 0;
};
template<class Ret, class Func>
struct ConcreteFunc : public FuncInterface<Ret> {
Func func;
Ret operator()() override {
func();
}
FuncInterface* clone() const override {
return new ConcreteFunc<Ret, Func>{func};
}
};
Let's add a helper function to make concrete funcs using bind_args:
auto makeConcrete = [](auto&& func, auto&&... args) {
using Func = decltype(bind(func, args...));
using Ret = decltype(const_cast<Func&>(bind(func, args...))());
return ConcreteFunc<Ret, Func>{bind(func, args...)};
}
We can write a wrapper class that automatically handles ownership:
template<class Ret>
struct AnyFunc {
std::unique_ptr<FuncInterface> func;
AnyFunc() = default;
AnyFunc(AnyFunc const& f) : func(f.func->clone()) {}
AnyFunc(AnyFunc&& f) = default;
explicit AnyFunc(FuncInterface* func) : func(func) {}
Ret operator()() {
return (*func)();
};
};
And then we can write a WorkerContainer:
struct WorkerContainer {
std::vector<AnyFunc> funcs;
template<class Worker, class... Args>
void addWorker(Worker&& worker, Args&&... args) {
auto func = [=](auto&&... args) {
worker.execute(args...);
};
FuncInterface* ptr = new auto(makeConcrete(func, args...));
func.emplace_back(ptr);
}
};
If you have defaulted values for arguments, you can re-write this to provide them like so:
template<class... DefaultArgs>
struct WorkerContainer {
std::vector<AnyFunc> funcs;
std::tuple<DefaultArgs...> storage;
template<class Worker, class... Args>
void addWorker(Worker&& worker) {
auto func = [=, This=this]() {
worker.execute(std::get<Args>(This->storage)...);
};
FuncInterface* ptr = new auto(makeConcrete(func));
func.emplace_back(ptr);
}
};
while doing one task I came up across this tricky implementation that would allow you to make constexpr lambdas (which is not allowed out of the box):
Crazy constexpr lambdas implementation article
It basicly boils down to this implementation:
template<class F>
struct wrapper
{
//static_assert(std::is_empty<F>(), "Lambdas must be empty");
template<class... Ts>
decltype(auto) operator()(Ts&&... xs) const
{
return reinterpret_cast<const F&>(*this)(std::forward<Ts>(xs)...);
}
};
struct wrapper_factor
{
template<class F>
constexpr wrapper<F> operator += (F*)
{
return{};
}
};
struct addr_add
{
template<class T>
friend typename std::remove_reference<T>::type* operator+(addr_add, T &&t)
{
return &t;
}
};
#define STATIC_LAMBDA wrapper_factor() += true ? nullptr : addr_add() + []
usage
const constexpr auto add_one = STATIC_LAMBDA (/*i believe you can pass arguments here but i havent tried*/) -> bool
{
//do stuff
return stuff;
};
I have got 2 questions:
I needed to comment out static_assert due to "std::is_empty<_Ty>': no appropriate default constructor available" even without making any instance of this lambda. Anyone knows why?
How does this even work? I followed the flow of all the classes and I understand that
a) this part
true ? nullptr : addr_add() + [] your_lambda
returns a nullptr of a type we want lambda to be (further refered to as "correct_type" )
b) wrapper_factory takes this nullptr of correct_type, constructs wrapper. This wrapper is default initialized.
c) wrapper in operator() forwards the arguments that he is called with (passed in the /* i believe.../* place) to an object that is "wrapper this pointer casted to correct_type".
Now where is the information what routine to call actually passed? I can only see default created empty wrapper that is reinterpreted to some lambda type and called via ().
Best regards
Marcin K.
So consider this code
#include <type_traits>
template<class F>
struct wrapper
{
static_assert(std::is_empty<F>::value, "Lambdas must be empty");
template<class... Ts>
decltype(auto) operator()(Ts&&... xs) const
{
return reinterpret_cast<const F&>(*this)(std::forward<Ts>(xs)...);
}
};
struct wrapper_factor
{
template<class F>
constexpr wrapper<F> operator += (F*)
{
return{};
}
};
struct addr_add
{
template<class T>
friend typename std::remove_reference<T>::type *operator+(addr_add, T &&t)
{
return &t;
}
};
#define STATIC_LAMBDA wrapper_factor() += true ? nullptr : addr_add() + []
const constexpr auto add_one = STATIC_LAMBDA(auto x)
{
return x + 1;
};
int main()
{
int a = 1;
int b = add_one(a);
return 0;
}
So in MSVS2015, add_one is reported as a constexpr wrapper<type> add_one = {}
But in main, add_one(a) is reported as int wrapper<lambda []auto (auto x)->auto>::operator() <int &>(int &xs) const
And you can see that the friend function *operator+ in addr_add returns the pointer to the lambda function. And the lambda function definition is implicit. The variadic template in the wrapper struct operator() will mimic the lambda arguments.
Is this what you wanted to know?
I am trying to implement a resource protection class which would combine data along with a shared mutex (actually, QReadWriteLock, but it's similar). The class must provide the method to apply a user-defined function to the data when the lock is acquired. I would like this apply method to work differently depending on the function parameter (reference, const reference, or value). For example, when the user passes a function like int (const DataType &) it shouldn't block exclusively as we are just reading the data and, conversely, when the function has the signature like void (DataType &) that implies data modification, hence the exclusive lock is needed.
My first attempt was to use std::function:
template <typename T>
class Resource1
{
public:
template <typename Result>
Result apply(std::function<Result(T &)> &&f)
{
QWriteLocker locker(&this->lock); // acquire exclusive lock
return std::forward<std::function<Result(T &)>>(f)(this->data);
}
template <typename Result>
Result apply(std::function<Result(const T &)> &&f) const
{
QReadLocker locker(&this->lock); // acquire shared lock
return std::forward<std::function<Result (const T &)>>(f)(this->data);
}
private:
T data;
mutable QReadWriteLock lock;
};
But std::function doesn't seem to restrict parameter constness, so std::function<void (int &)> can easily accept void (const int &), which is not what I want. Also in this case it can't deduce lambda's result type, so I have to specify it manually:
Resource1<QList<int>> resource1;
resource1.apply<void>([](QList<int> &lst) { lst.append(11); }); // calls non-const version (ok)
resource1.apply<int>([](const QList<int> &lst) -> int { return lst.size(); }); // also calls non-const version (wrong)
My second attempt was to use std::result_of and return type SFINAE:
template <typename T>
class Resource2
{
public:
template <typename F>
typename std::result_of<F (T &)>::type apply(F &&f)
{
QWriteLocker locker(&this->lock); // lock exclusively
return std::forward<F>(f)(this->data);
}
template <typename F>
typename std::result_of<F (const T &)>::type apply(F &&f) const
{
QReadLocker locker(&this->lock); // lock non-exclusively
return std::forward<F>(f)(this->data);
}
private:
T data;
mutable QReadWriteLock lock;
};
Resource2<QList<int>> resource2;
resource2.apply([](QList<int> &lst) {lst.append(12); }); // calls non-const version (ok)
resource2.apply([](const QList<int> &lst) { return lst.size(); }); // also calls non-const version (wrong)
Mainly the same thing happens: as long as the object is non-const the mutable version of apply gets called and result_of doesn't restrict anything.
Is there any way to achieve this?
You may do the following
template <std::size_t N>
struct overload_priority : overload_priority<N - 1> {};
template <> struct overload_priority<0> {};
using low_priority = overload_priority<0>;
using high_priority = overload_priority<1>;
template <typename T>
class Resource
{
public:
template <typename F>
auto apply(F&& f) const
// -> decltype(apply_impl(std::forward<F>(f), high_priority{}))
{
return apply_impl(std::forward<F>(f), high_priority{});
}
template <typename F>
auto apply(F&& f)
// -> decltype(apply_impl(std::forward<F>(f), high_priority{}))
{
return apply_impl(std::forward<F>(f), high_priority{});
}
private:
template <typename F>
auto apply_impl(F&& f, low_priority) -> decltype(f(std::declval<T&>()))
{
std::cout << "ReadLock\n";
return std::forward<F>(f)(this->data);
}
template <typename F>
auto apply_impl(F&& f, high_priority) -> decltype(f(std::declval<const T&>())) const
{
std::cout << "WriteLock\n";
return std::forward<F>(f)(this->data);
}
private:
T data;
};
Demo
Jarod has given a workaround, but I'll explain why you cannot achieve that this regular way.
The problem is that:
Overload resolution prefers non-const member functions over const member functions when called from a non-const object
whatever object this signature void foo(A&) can accept, void foo(const A&) can also the same object. The latter even has a broader binding set than the former.
Hence, to solve it, you will have to at least defeat point 1 before getting to 2. As Jarod has done.
From your signatures (see my comment annotations):
template <typename F>
typename std::result_of<F (T &)>::type apply(F &&f) //non-const member function
{
return std::forward<F>(f)(this->data);
}
template <typename F>
typename std::result_of<F (const T &)>::type apply(F &&f) const //const member function
{
return std::forward<F>(f)(this->data);
}
When you call it like:
resource2.apply([](QList<int> &lst) {lst.append(12); }); //1
resource2.apply([](const QList<int> &lst) { return lst.size(); }); //2
First of all, remember that resource2 isn't a const reference. Hence, the non-const membr function of apply will always be prefered by Overload resolution.
Now, taking the case of the first call //1, Whatever that lambda is callable with, then then the second one is also callable with that object
A simplified mock-up of what you are trying to do is:
struct A{
template<typename Func>
void foo(Func&& f); //enable if we can call f(B&);
template<typename Func>
void foo(Func&& f) const; //enable if we can call f(const B&);
};
void bar1(B&);
void bar2(const B&);
int main(){
A a;
a.foo(bar1);
a.foo(bar2);
//bar1 and bar2 can be both called with lvalues
B b;
bar1(b);
bar2(b);
}
As I understand it, you want to discriminate a parameter that's a std::function that takes a const reference versus a non-constant reference.
The following SFINAE-based approach seems to work, using a helper specialization class:
#include <functional>
#include <iostream>
template<typename ...Args>
using void_t=void;
template<typename Result,
typename T,
typename lambda,
typename void_t=void> class apply_helper;
template <typename T>
class Resource1
{
public:
template <typename Result, typename lambda>
Result apply(lambda &&l)
{
return apply_helper<Result, T, lambda>::helper(std::forward<lambda>(l));
}
};
template<typename Result, typename T, typename lambda, typename void_t>
class apply_helper {
public:
static Result helper(lambda &&l)
{
std::cout << "T &" << std::endl;
T t;
return l(t);
}
};
template<typename Result, typename T, typename lambda>
class apply_helper<Result, T, lambda,
void_t<decltype( std::declval<lambda>()( std::declval<T>()))>> {
public:
static Result helper(lambda &&l)
{
std::cout << "const T &" << std::endl;
return l( T());
}
};
Resource1<int> test;
int main()
{
auto lambda1=std::function<char (const int &)>([](const int &i)
{
return (char)i;
});
auto lambda2=std::function<char (int &)>([](int &i)
{
return (char)i;
});
auto lambda3=[](const int &i) { return (char)i; };
auto lambda4=[](int &i) { return (char)i; };
test.apply<char>(lambda1);
test.apply<char>(lambda2);
test.apply<char>(lambda3);
test.apply<char>(lambda4);
}
Output:
const T &
T &
const T &
T &
Demo
The helper() static class in the specialized class can now be modified to take a this parameter, instead, and then use it to trampoline back into the original template's class's method.
As long as the capture lists of your lambdas are empty, you can rely on the fact that such a lambda decays to a function pointer.
It's suffice to discriminate between the two types.
It follows a minimal, working example:
#include<iostream>
template <typename T>
class Resource {
public:
template <typename Result>
Result apply(Result(*f)(T &)) {
std::cout << "non-const" << std::endl;
return f(this->data);
}
template <typename Result>
Result apply(Result(*f)(const T &)) const {
std::cout << "const" << std::endl;
return f(this->data);
}
private:
T data;
};
int main() {
Resource<int> resource;
resource.apply<void>([](int &lst) { });
resource.apply<int>([](const int &lst) -> int { return 42; });
}
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.