部分特化可变参数模板的方法 类

Partially specialize methods of variadic template classes

我想编写一个解组器来提取存储在 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  

看来我可以对整个 class:

进行部分特化
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 { ... };

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

在 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 returns 一个 lambda。此 lambda 接受另一个 lambda,然后使用从 0 到 N-1 的编译时间常量调用它。它通过调用 index_over 来完成此操作,它采用索引列表。

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

组合 index_uptoforeach 让您可以为从 0N-1 的每个编译时值做一些事情。这就是我们所说的 .convert.

只需调用 index_upto 即可让我们将所有参数一次传递给 emit


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

我一直觉得特化一个成员函数最容易维护的方法是遵从一个特化的函数对象:

#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());
}