我正在尝试编写一个具有模板化构造函数的类Invocation
:
template<typename F>
class Invocation {
public:
template<typename... Args>
Invocation(F&& f, Args&&... args)
{ /* store f and args somewhere for later use */ }
...
};
通常情况下,我会使用Invocation
和F
参数化Args...
类本身,但在这种情况下,我需要一个给定F
的统一类型,所以我&# 39;我试图找到一种方法来存储args...
内任何类型的Invocation<F>
,并尽可能减少性能。 (这可能不是最好的设计,但它可能是一项有趣的练习。)
一种想法是使用虚函数:
template<typename F>
class ArgsBase {
public:
// discard return value
virtual void invoke(F&& f) = 0;
};
template<typename F, typename... Ts>
class Args : public ArgsBase<F> {
public:
Args(Ts&&... args) : args_(std::forward<Ts>(args)...) {}
void invoke(F&& f) override
{
/* somehow call f with args_ (something like std::apply) */
...
}
private:
std::tuple<Ts&&...> args_;
};
然后在Invocation<F>
类中,我们可以拥有一个std::unique_ptr<ArgsBase<F>>
成员,该成员指向Args<F, Ts...>
ctor中创建的Invocation<F>
对象。我们可以在需要时调用其invoke
虚拟方法。
这只是我提出的一个随意的想法。有没有其他方法来实现这一目标?理想情况下,没有虚拟功能的开销或类似的东西?
更新:感谢使用std::function
或lambdas建议的评论/答案。我应该清楚地表明我实际上对更一般的情况感兴趣,即可变参数可能不是可调用的参数。它可以是我想要存储在类中的任何类型,其类型不是由这些东西的类型参数化。
答案 0 :(得分:1)
如果您尝试使用其参数保存函数调用以供以后调用,则可以使用std::function
个对象中打包的lambdas:
template<typename F, typename ... Args>
std::function<void()> createInvocation(F f, const Args& ... args)
{
return [f,args...]() { f(args...); };
}
然后你可以像这样使用它:
void myFunc(int a, int b)
{
std::cout << "Invoked: " << a + b << std::endl;
}
int main() {
auto invocation = createInvocation(myFunc, 1, 2);
invocation();
return 0;
}
更新:如果要创建通用的非模板化容器类型,可以将元组包装为自身派生自非模板化类型的类型。那么主要问题是访问底层数据。这可以通过创建静态函数调度表来解决,该表针对给定的元组类型重定向查询,以便可以使用动态提供的函数参数调用需要编译时常量索引模板参数的std::get
。以下是实现此目的的实现:
class GenericTupleContainer
{
public:
virtual const void* getItemAtIndex(size_t index) = 0;
};
template<typename ... T>
class TupleContainer : public GenericTupleContainer
{
public:
TupleContainer(T&& ... args)
: data(std::forward<T>(args)...)
{}
const void* getItemAtIndex(size_t index) override
{
if(index >= sizeof...(T))
throw std::runtime_error("Invalid index");
return dispatchTable[index](data);
}
private:
template<size_t index>
static const void* getItemAtIdx(const std::tuple<T...>& data)
{
return &std::get<index>(data);
}
using GetterFn = const void*(*)(const std::tuple<T...>&);
static GetterFn* initDispatchTable()
{
static GetterFn dispatchTable[sizeof...(T)];
populateDispatchTable<sizeof...(T)>(dispatchTable, std::integral_constant<bool, sizeof...(T) == 0>());
return dispatchTable;
}
static GetterFn* dispatchTable;
template<size_t idx>
static void populateDispatchTable(GetterFn* table, std::false_type);
template<size_t idx>
static void populateDispatchTable(GetterFn* table, std::true_type)
{
//terminating call - do nothing
}
std::tuple<T...> data;
};
template<typename ... T>
typename TupleContainer<T...>::GetterFn* TupleContainer<T...>::dispatchTable = TupleContainer<T...>::initDispatchTable();
template<typename ... T>
template<size_t idx>
void TupleContainer<T...>::populateDispatchTable(GetterFn* table, std::false_type)
{
table[idx-1] = &TupleContainer<T...>::template getItemAtIdx<idx-1>;
populateDispatchTable<idx-1>(table, std::integral_constant<bool, idx-1 == 0>() );
}
template<typename ... T>
auto createTupleContainer(T&& ... args)
{
return new TupleContainer<T...>(std::forward<T>(args)...);
}
然后您可以按如下方式使用上述内容:
int main() {
GenericTupleContainer* data = createTupleContainer(1, 2.0, "Hello");
std::cout << *(static_cast<const int*>(data->getItemAtIndex(0))) << std::endl;
std::cout << *(static_cast<const double*>(data->getItemAtIndex(1))) << std::endl;
std::cout << (static_cast<const char*>(data->getItemAtIndex(2))) << std::endl;
return 0;
}
从上面的用法中可以看出,您已经实现了将任意模板化元组包装为非模板化类型的目的,这样您就可以使用普通(函数)索引参数访问组件成员而不是模板之一。现在这种吸气剂的返回类型必须是通用的,所以我选择在这里使用void*
,这是不理想的。但是你可以开发这个想法,使这个容器提供有关其数据元组成员类型的更多有用信息。另请注意,这确实使用了虚函数。通过一些进一步的工作你也可以摆脱这个,虽然你将无法摆脱至少一个函数指针查找(即在调度表中的查找) - 这是为获得灵活性而支付的价格能够使用运行时值来索引元组。
答案 1 :(得分:1)
如评论中所述,我不担心按值存储参数。编译器的copy-elision可以很慷慨。
特别是如果你为类提供一个r值调用:
#include <tuple>
template<typename F>
class ArgsBase {
public:
// discard return value
virtual void invoke(F&& f) const & = 0;
virtual void invoke(F&& f) && = 0;
};
template<typename F, class... FunctionArgs>
class Args : public ArgsBase<F> {
public:
template<class...Ts>
Args(Ts&&... args) : args_(std::forward<Ts>(args)...) {}
template<std::size_t...Is, class Tuple>
static void invoke_impl(F& f, std::index_sequence<Is...>, Tuple&& t)
{
f(std::get<Is>(std::forward<Tuple>(t))...);
}
void invoke(F&& f) const & override
{
invoke_impl(f,
std::make_index_sequence<std::tuple_size<tuple_type>::value>(),
args_);
/* somehow call f with args_ (something like std::apply) */
}
void invoke(F&& f) && override
{
invoke_impl(f,
std::make_index_sequence<std::tuple_size<tuple_type>::value>(),
std::move(args_));
/* somehow call f with args_ (something like std::apply) */
}
private:
using tuple_type = std::tuple<FunctionArgs...>;
tuple_type args_;
};
template<class Callable, class...MyArgs>
auto later(MyArgs&&...args) {
return Args<Callable, std::decay_t<MyArgs>...>(std::forward<MyArgs>(args)...);
}
void foo(const std::string&, std::string)
{
}
int main()
{
auto l = later<decltype(&foo)>(std::string("hello"), std::string("world"));
l.invoke(foo);
std::move(l).invoke(foo);
}