这是对这个问题的后续回答。
在这个问题中,我有一个函数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(); }
与其试图让模板参数推导出WidgetType
和Dependencies...
,不如让package
接受任何类型,并设计类型特征从中计算出WidgetType
和Dependencies
。
可能的实现:
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_ptr
s的元组,以传递给类似函数的东西。
这允许package
和accept
接受任何类型,并通过FunctionTraits
和ArgsTuple
计算出WidgetType
和Dependencies
。