繁体   English   中英

非模板类的可变参数成员

[英]Variadic members in non-template class

我正在尝试编写具有模板构造函数的类Invocation

template<typename F>
class Invocation {
public:
    template<typename... Args>
    Invocation(F&& f, Args&&... args)
    { /* store f and args somewhere for later use */ }

    ...
};

通常我会同时使用FArgs...参数化Invocation类本身,但是在这种情况下,我需要给定F的统一类型,所以我试图找到一种方法来存储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>>成员,该成员指向在Invocation<F> ctor中创建的Args<F, Ts...>对象。 。 我们可以在需要时调用其invoke虚拟方法。

这只是我提出的一个随机想法。 还有其他方法可以做到这一点吗? 理想情况下没有虚拟功能或类似功能的开销?

更新:感谢建议使用std::function或lambdas的评论/答案。 我应该明确指出,我实际上对更一般的情况感兴趣,即,可变参数可能不是可调用对象的参数。 它可以是我想存储在其类型未由这些东西的类型参数化的类中的任何东西。

如果您尝试保存带有其参数的函数调用以供以后调用,则可以使用打包在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* ,这不是理想的选择。 但是您可以开发出这种想法,以使该容器提供有关其数据元组成员类型的更多有用信息。 另外,请注意,这确实使用了虚函数。 通过进一步的工作,您也可以摆脱这种情况,尽管您将无法摆脱至少一个函数指针查找(即调度表中的查找)-这是获得灵活性的代价。能够使用运行时值索引到元组。

如评论中所述,我不必担心按值存储参数。 编译器的复制删除功能可能很慷慨。

特别是如果您为该类提供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);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM