【问题标题】:Create arguments and forward from variadic type template从可变参数类型模板创建参数和转发
【发布时间】:2019-04-12 20:51:50
【问题描述】:

我正在尝试从可变参数模板创建参数并将其转发到存储函数。如果参数是(typename... Args),我想迭代每种类型并从存储容器中获取该类型的参数,然后将参数转发给函数。

我尝试了不同的方法,但最后总是无法存储无类型的参数向量,也无法将向量作为单独的参数转发。

这是我想要完成的一些伪代码。

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
}

ArgumentList 类型没有实现(并且可能无法实现),但这是我正在尝试创建的系统。

编辑:更多解释

这是我的系统的外观:

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

用法如下所示:

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

我想创建一个干净的 API,您可以在其中创建一个具有包含执行函数的简单结构的 Worker。然后,参数的类型将用于尝试获取存储在容器中的每种类型的实例的引用。然后将其发送到执行函数。这个想法来自Game Engine talk

【问题讨论】:

  • 您在寻找std::tuplestd::apply() 吗?
  • 解决这个问题需要了解两件事:1)参数最初来自哪里,2)那个函数来自哪里?您能否提供一个示例用法/解释您尝试将其用于什么任务?
  • 我做了很多与你想做的非常相似的事情;我只是不太清楚你要做什么
  • @JorgePerez 添加了更多解释
  • 你看过std::tuple吗?您还可以使用 std::function 进行类型擦除。

标签: c++ metaprogramming c++17 variadic-templates template-meta-programming


【解决方案1】:

我们可以很容易地将函数和参数绑定在一起,以便稍后调用:

auto bind_args = [](auto&& func, auto&&... args) {
    return [=]() mutable {
        return func(args...); 
    };
};

我们可以擦除这个类型,以便以后也可以轻松执行:

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}; 
    }
};

让我们添加一个辅助函数来使用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...)}; 
}

我们可以编写一个自动处理所有权的包装类:

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)(); 
    };
}; 

然后我们可以写一个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); 
    }
}; 

如果您有参数的默认值,您可以重写它以提供它们,如下所示:

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

【讨论】:

  • 不确定我是否遗漏了一些东西,但这需要用户手动引用像 WorkerContainer.add(new TestWorker(), storage.get&lt;SomeStruct&gt;(), storage.get&lt;AnotherStruct&gt;()); 这样的 args,而我正试图通过让 args 动态了解类型来简化它函数参数
  • 我添加了一个示例,展示如何从存储中获取它们
  • 好吧,使用std::function 会简化事情。顺便说一句,如果重新分配抛出,您可能会泄漏内存,也许在其他时候也是如此。
猜你喜欢
  • 1970-01-01
  • 2014-06-07
  • 1970-01-01
  • 1970-01-01
  • 2016-01-22
  • 1970-01-01
  • 2011-09-23
  • 2022-09-22
  • 1970-01-01
相关资源
最近更新 更多