【问题标题】:Partially specialize methods of variadic template classes可变参数模板类的部分特化方法
【发布时间】:2017-08-13 12:40:32
【问题描述】:

我想编写一个解组器来提取存储在 msgpack 数组中的参数,用于调用 sigc::signal::emit(...) 的单个参数。我试过这个:

template<class... T> class MsgpackAdapter: public MsgpackAdapterBase {
public:
    MsgpackAdapter (sigc::signal<void, T...> &sig)
            : MsgpackAdapterBase (), signal_ (sig)
    {}
protected:
    virtual void do_emit (const msgpack::object_array &mp_args) override;
private:
    sigc::signal<void, T...> signal_;
};

template<class T1>
void MsgpackAdapter<T1>::do_emit (const msgpack::object_array &mp_args)
{
    T1 a1;
    mp_args.ptr[0].convert (a1);
    signal_.emit (a1);
}

template<class T1, class T2>
void MsgpackAdapter<T1, T2>::do_emit (const msgpack::object_array &mp_args)
{
    T1 a1;
    T2 a2;
    mp_args.ptr[0].convert (a1);
    mp_args.ptr[1].convert (a2);
    signal_.emit (a1, a2);
}

以此类推,最多 4 个参数。但我收到此错误消息(来自 vim-youcompleteme 使用 clang 3.9):

'MsgpackAdapter<T1>::' for declaration does not refer into a class, class template or class template partial specialization  

看来我可以对整个班级进行部分专业化:

template<class T1> class MsgpackAdapter<T1>: public MsgpackAdapterBase { ... };

但我宁愿只专门化 emit 方法来减少复制和粘贴代码的数量。我错过了什么明显的东西吗?我认为主要的困难是do_emit 不接受模板化参数。

另一个好奇是,如果我尝试在没有可变参数模板的情况下这样做,并使用:

template<class T1> class MsgpackAdapter: public MsgpackAdapterBase { ... };
template<class T1, class T2> class MsgpackAdapter: public MsgpackAdapterBase { ... };

我收到第二个类定义与第一个类定义冲突的错误。这是可以理解的,但我想知道 sigc 如何在没有可变参数模板的情况下管理类似的事情。

【问题讨论】:

  • 使用由 doEmit 调用的辅助模板类,并对其进行专门化
  • 如果不专门化整个类,就不能单独专门化一个成员。
  • 为什么要写 N 个版本,为什么不写一个呢?在 C++14 中容易,在 C++11 中更难,但总比重复自己要好。我说重复自己很糟糕吗?值得重复。

标签: c++ c++11 variadic-templates unmarshalling template-specialization


【解决方案1】:

我一直觉得对成员函数进行特化最易维护的方式就是顺从一个特化的函数对象:

#include <iostream>

struct object_array
{
};


template<class...Ts>
struct implement_do_emit
{
    template<class This>
    void operator ()(This *that, const object_array& arg) const;
};


template<class... Ts>
struct MsgpackAdapter
{
    friend class implement_do_emit<Ts...>;

    virtual void do_emit(const object_array& mp_args)
    {
        auto impl = implement_do_emit<Ts...>();
        impl(this, mp_args);
    }

};

template<class T>
struct implement_do_emit<T>
{
    template<class This>
    void operator ()(This *that, const object_array& arg) const
    {
        std::cout << "one type version\n";
    }
};

template<class T1, class T2>
struct implement_do_emit<T1, T2>
{
    template<class This>
    void operator ()(This *that, const object_array& arg) const
    {
        std::cout << "two type version\n";
    }
};


int main()
{
    MsgpackAdapter<int> pi {};
    pi.do_emit(object_array());

    MsgpackAdapter<int, int> pii {};
    pii.do_emit(object_array());
}

【讨论】:

    【解决方案2】:

    在 C++14 中:

    template<class F>
    auto foreach( F&& f ) {
      return [f=std::forward<F>(f)](auto&&...args)mutable{
        using discard=int[];
        (void)discard{0,(void(
          f(decltype(args)(args))
        ),0)...};
      };
    }
    template<std::size_t...Is>
    auto index_over( std::index_sequence<Is...> ) {
      return [](auto&& f)->decltype(auto){
        return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
      };
    }
    template<std::size_t N>
    auto index_upto( std::integral_constant<std::size_t, N> ={} ) {
      return index_over( std::make_index_sequence<N>{} );
    }
    
    
    template<class...Ts>
    void MsgpackAdapter<Ts...>::do_emit (const msgpack::object_array &mp_args)
    {
      std::tuple<Ts...> args;
      index_upto<sizeof...(Ts)>()(
        foreach(
          [&](auto I){
            mp_args.ptr[I].convert(std::get<I>(args));
          }
        )
      );
      index_upto<sizeof...(Ts)>()(
        [&](auto...Is){
          signal_.emit(std::get<Is>(args)...);
        }
      );
    }
    

    或类似的。 Live example.

    基本上,创建一个元组。

    在该元组中创建一组索引。

    对于元组中的每个索引,调用转换。

    然后,调用 emit 获取元组的每个元素。

    有许多堆栈溢出代码示例涉及将元组的每个参数传递给函数调用。那是发射部分。

    有很多关于为元组的每个元素做某事的堆栈溢出的例子。使用索引这样做有点棘手,但最坏的情况是您可以计算每个元素是否按顺序执行操作。

    这些可以在 C++11 中完成,但在 C++14 中,我可以在没有辅助函数的函数中完成所有这些操作。


    上述魔法代码的说明。 index_upto 返回一个 lambda。这个 lambda 接受另一个 lambda,然后使用从 0 到 N-1 的编译时间常数调用它。它通过调用index_over 来实现这一点,它采用索引列表。

    foreach 采用 lambda f。然后它返回一个接受任意数量参数的 lambda,并使用这些参数中的每一个调用一次 f。它的实现有点深奥,涉及参数包和数组初始化。

    组合 index_uptoforeach 可以让您对从 0N-1 的每个编译时值执行一些操作。这就是我们如何称呼.convert

    只需调用index_upto,我们就可以一次将所有参数传递给emit


    我们可以在 C++11 中做类似的事情,但我们会编写带有参数包等的辅助函数。这不只是有点痛苦。

    【讨论】:

    猜你喜欢
    • 2012-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-25
    • 1970-01-01
    • 1970-01-01
    • 2023-03-13
    相关资源
    最近更新 更多