我想构建一个漂亮的现代界面来构建计算树,如下所示:
auto [F, G] = calcs.emplace(
[](int a, int b){ return a + b; },
[](){ return 4; }
);
我从taskflow获得灵感,但在这里我们可能会添加参数和返回类型,这就是问题所在:我们如何推断给定Callable对象的底层存储类型,以及如何将它们存储在集合中?是否可以使用当前的语言功能创建这样一个简单的api?
我用谷歌搜索了几个小时,看起来返回类型是一个较小的问题,但我不知道这些论点。
问:如何获得签名?
答:通过可调用的operator()
方法的模式匹配,例如:
template <class TMethodType>
struct ReadSignature;
// Const specialization
template <class TReturnType, class TClass, class ... TArgsTypes>
struct ReadSignature<TReturnType(TClass::*)(TArgsTypes...) const> {
using ReturnType = TReturnType;
using Class = TClass;
using Args = std::tuple<TArgsTypes...>;
static constexpr bool is_const = true;
};
// Non-const specialization
// This is for mutable lambdas, e.g. []() mutable {}
template <class TReturnType, class TClass, class ... TArgsTypes>
struct ReadSignature<TReturnType(TClass::*)(TArgsTypes...)> {
using ReturnType = TReturnType;
using Class = TClass;
using Args = std::tuple<TArgsTypes...>;
static constexpr bool is_const = false;
};
你这样使用它:
auto callable = [](int x, int y) { return x + y; };
using Class = decltype(callable);
using Signature = ReadSignature<decltype(&Class::operator())>;
问:如何在一个集合中存储callable?
答:您需要以某种方式擦除类型。对于可调用对象,使包装器界面看起来很自然。例如,像这样:
class CallableI {
virtual ~CallableI() = default;
virtual std::any call(const std::vector<std::any>& args) = 0;
// For type checking:
virtual size_t arg_count() = 0;
virtual std::type_info get_arg_type_info(size_t arg_index) = 0;
virtual std::type_info get_return_type_info() = 0;
};
然后编写一个实现此接口的模板类,该接口将针对每个lambda参数进行实例化。在你的calcs
对象中,你实际上存储std::vector<std::unique_ptr<CallableI>>
。