将捕获的lambda作为参数传递给函数



这是对这个问题的后续回答。

在这个问题中,我有一个函数package,函数指针作为参数。它接受的函数的签名是这样的:

template <typename WidgetType, typename Deleter, typename... Dependencies>
using FactoryFunction = std::unique_ptr<WidgetType, Deleter>(*)(std::shared_ptr<Dependencies>...); 

package的特征是这样的:

template <typename WidgetType, typename Deleter, typename... Dependencies>
void package(FactoryFunction<WidgetType, Deleter, Dependencies...> factoryFunction)

我能够通过使用一元操作符将lambda传递给package:

package(+[]{return std::make_unique<Foo>()});

一切都很好,除非我需要在传递给package的lambda中捕获一些东西。如果我将别名FactoryFunction更改为:

template <typename WidgetType, typename Deleter, typename... Dependencies>
using FactoryFunction = std::function<std::unique_ptr<WidgetType, Deleter>(std::shared_ptr<Dependencies>...)>;

然后我可以通过以下操作将捕获lambda传递给package:

std::function<std::unique_ptr<Foo>()> fooer = [i](){
return std::make_unique<Foo>();
};
package(fooer);

正如在最初的问题中提到的,这是相当冗长的,它涉及到大量的冗余,特别是当有更多的依赖类型时。

std::function<std::unique_ptr<Bar>(
std::shared_ptr<Foo>, std::shared_ptr<Bif>, std::shared_ptr<Baz>)> barer= 
[i](std::shared_ptr<Foo> foo, std::shared_ptr<Bif> bif, std::shared_ptr<Baz> baz) {
return std::make_unique<Bar>(foo, bif, baz);
};
package(fooer);

我如何写package的签名,以便我可以用简洁的东西调用package:

package([capturedVar](std::shared_ptr<Foo> foo, std::shared_ptr<Bif> bif, std::shared_ptr<Baz> baz) {
foo->proccess(capturedVar);
return std::make_unique<Bar>(foo, bif, baz);
});

为了子孙后代,这里是原始问题的代码。

#include <functional>
#include <iostream>
#include <memory>
#include <typeindex>
#include <unordered_map>
// Template alias for widget factory.
template <typename WidgetType, typename Deleter, typename... Dependencies>
using FactoryFunction = std::unique_ptr<WidgetType, Deleter> (*)(std::shared_ptr<Dependencies>...);
// A pair of widgets, one dependant on the other.
struct Foo {
Foo() { std::cout << "Foo constructed.n"; }
};
struct Bar {
Bar(std::shared_ptr<Foo> f) : f_(f) { std::cout << "Bar constructed from Foo ptr.n"; }
std::shared_ptr<Foo> f_;
};
// Factory functions to make widgets.
std::unique_ptr<Foo> makeFoo() { return std::make_unique<Foo>(); }
std::unique_ptr<Bar> makeBar(std::shared_ptr<Foo> foo) { return std::make_unique<Bar>(foo); }
// Map of factory functions packaged into function of signature void(), keyed by the type index of the widget type it makes.
std::unordered_map<std::type_index, std::function<void()>> packagedFactories;
// Final resting place of a factory function.
template <typename WidgetType, typename Deleter, typename... Dependencies>
std::shared_ptr<WidgetType> accept(FactoryFunction<WidgetType, Deleter, Dependencies...> factoryFunction) {
return std::invoke(factoryFunction, std::make_shared<Dependencies>()...);
// This part has a bit more going on in the real code.
// Rather than making a new widget for each dependency,
// dependencies are created elsewhere and fetched.
// Something like:
// return std::invoke(factoryFunction, getWidget<Dependencies>()...);
}
// Package a factory for later invocation.
template <typename WidgetType, typename Deleter, typename... Dependencies>
void package(FactoryFunction<WidgetType, Deleter, Dependencies...> factoryFunction) {
auto tIndex = std::type_index(typeid(WidgetType));
packagedFactories[tIndex] = [factoryFunction]() { accept(factoryFunction); };
}
void someFunc() {
package(makeFoo);
// package(+[]() { return std::unique_ptr<Foo>; }); // This works too.
int i = 6;
package(makeBar);
//  package([i](std::shared_ptr<Foo> foo) {  // <- This gives me errors.
//      foo->processThing(i);
//      return std::make_unique<Bar>(foo);
//  }
packagedFactories[std::type_index(typeid(Foo))]();
packagedFactories[std::type_index(typeid(Bar))]();
}
int main(int argc, char* argv[]) { someFunc(); }

与其试图让模板参数推导出WidgetTypeDependencies...,不如让package接受任何类型,并设计类型特征从中计算出WidgetTypeDependencies

可能的实现:

template <typename T>
struct FunctionTraits
{
using ReturnType = typename FunctionTraits<decltype(&T::operator())>::ReturnType;
using ArgumentTypes = typename FunctionTraits<decltype(&T::operator())>::ArgumentTypes;
};
template <typename T, typename Ret, typename... Args>
struct FunctionTraits<Ret(T::*)(Args...)>
{
using ReturnType = Ret;
using ArgumentTypes = std::tuple<Args...>;
};
template <typename T, typename Ret, typename... Args>
struct FunctionTraits<Ret(T::*)(Args...) const>
{
using ReturnType = Ret;
using ArgumentTypes = std::tuple<Args...>;
};
template <typename Ret, typename... Args>
struct FunctionTraits<Ret(*)(Args...)>
{
using ReturnType = Ret;
using ArgumentTypes = std::tuple<Args...>;
};
template <typename T>
struct ArgsTuple;
template <typename... Args>
struct ArgsTuple<std::tuple<std::shared_ptr<Args>...>>
{
static auto make()
{
return std::make_tuple(std::make_shared<Args>()...);
}
};
std::unordered_map<std::type_index, std::function<void()>> packagedFactories;
template <typename FactoryFunction>
auto accept(FactoryFunction factoryFunction) {
auto args = ArgsTuple<typename FunctionTraits<FactoryFunction>::ArgumentTypes>::make();
return std::apply(factoryFunction, args);
}
template <typename FactoryFunction>
void package(FactoryFunction factoryFunction) {
using WidgetType = typename FunctionTraits<FactoryFunction>::ReturnType::element_type;
auto tIndex = std::type_index(typeid(WidgetType));
packagedFactories[tIndex] = [factoryFunction]() { accept(factoryFunction); };
}

现场演示

在这个例子中,FunctionTraits使用部分专门化来计算一个类似函数的东西的返回类型和参数类型(假设它没有多个operator()的重载)。ArgsTuple然后使用部分专门化来构建shared_ptrs的元组,以传递给类似函数的东西。

这允许packageaccept接受任何类型,并通过FunctionTraitsArgsTuple计算出WidgetTypeDependencies

最新更新