This is a follow up to this question .
In that question I had a function package
with a function pointer as a parameter. The signature of the functions it accepted looked like this:
template <typename WidgetType, typename Deleter, typename... Dependencies>
using FactoryFunction = std::unique_ptr<WidgetType, Deleter>(*)(std::shared_ptr<Dependencies>...);
And the signature of package
looked like this:
template <typename WidgetType, typename Deleter, typename... Dependencies>
void package(FactoryFunction<WidgetType, Deleter, Dependencies...> factoryFunction)
I was able to pass a lambda to package
by using the unary operator:
package(+[]{return std::make_unique<Foo>()});
All well and good, unless I need to capture something in the lambda passed to package
. If I change the alias FactoryFunction
to:
template <typename WidgetType, typename Deleter, typename... Dependencies>
using FactoryFunction = std::function<std::unique_ptr<WidgetType, Deleter>(std::shared_ptr<Dependencies>...)>;
Then I can pass a capturing lambda to package
by doing:
std::function<std::unique_ptr<Foo>()> fooer = [i](){
return std::make_unique<Foo>();
};
package(fooer);
As mentioned in the original question, this is quite verbose, and it involves a good amount of redundancy, especially when there are more dependency types.
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);
How can I write the signature of package
so that I can call package
with something as succinct as:
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);
});
For posterity, here's the code from the original question.
#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(); }
Instead of trying to let template argument deduction deduce WidgetType
and Dependencies...
, it may be better to let package
accept any type, and design type traits to figure out WidgetType
and Dependencies
from that.
Possible implementation:
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); };
}
In this example, FunctionTraits
uses partial specialization to figure out the return type and argument types of a function-like thing (assuming it doesn't have multiple overloads of operator()
). ArgsTuple
then uses partial-specialization to build a tuple of shared_ptr
s to pass to that function-like thing.
This allows package
and accept
to accept any type and figure out WidgetType
and Dependencies
using FunctionTraits
and ArgsTuple
.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.