避免专门化模板中的代码重复

Avoiding code duplication in a specialized template

本文关键字:代码 专门化      更新时间:2023-10-16

我想有两个类似的模板,一个有1个参数,另一个有2个参数:

template<typename T1, typename T2=void>
struct foo {
  T1 m_t1;
  T2 m_t2;
  foo(T1 t1, T2 t2) : m_t1(t1), m_t2(t2) {}
  T1 t1() { return m_t1; }
  T2 t2() { return m_t2; }
};
template<typename T1>
struct foo<T1,void> {
  T1 m_t1;
  foo(T1 t1) : m_t1(t1) {}
  T1 t1() { return m_t1; }
};

注意所有与t1相关的东西的代码复制。我怎样才能避免这种情况呢?

唯一的解决方案是在基类中包含尽可能多的代码。例如:

template<typename T1>
struct foo_base {
    T1 m_t1;
    explicit foo_base(T1 t1) : m_t1(t1) {}
    T1 t1() const { return m_t1; }
};
template<typename T1, typename T2=void>
struct foo : foo_base<T1> {
    T2 m_t2;
    foo(T1 t1, T2 t2) : foo_base<T1>(t1), m_t2(t2) {}
    T2 t2() const { return m_t2; }
};
template<typename T1>
struct foo<T1,void> : foo_base<T1> {
    explicit foo(T1 t1) : foo_base<T1>(t1) {}
};

这是一个非常普遍的问题。

在这种情况下,您可以将T1相关的内容放在基类中。一般来说,您应该避免"一二多"问题。您在这里所做的也可以通过std::tuple实现,它也是Boost库的一部分。

对于它的价值,tuple通过组成任意数量的基类来工作。

仔细查看您的代码,您正在重新实现std::tuple。将t1t2方法交换为自由函数std::get<N>,您将拥有std::tuple提供的一切(甚至更多)。为方便起见,如果作为方法,可以这样考虑:

template<typename... Ts>
struct foo {
  typedef std::tuple<Ts...> Tup;
  Tup m_ts;
  foo(Ts... ts) : m_ts{ts...} {} //!
  template <unsigned N> 
  std::tuple_element<N, Tup> t() { return std::get<N>(Tup); }
};

对于//!:当然,您可以将构造函数设置为(可变)模板,并将参数转发给元组。哦,访问器可以/应该为const和nonconst重载,并返回对元组元素的适当引用…

但是说真的,这并不值得你为之流汗。只使用普通的std::tuple。当然,除了你过分简化了问题,你做的事情与你告诉我们的不同。

继承可以解决您的问题。定义一个提供T1内容的单参数基类,并使双参数版本继承它。

有三个数字:0、1和无穷大。

哦,计数从0开始,不是1!

template<typename... Ts>
struct first_type {}
template<typename T0, typename... Ts>
struct first_type {
  typedef T0 type;
};
template<typename... Ts>
using FirstType = typename first_type<Ts...>::type;
template<typename T0, typename Rest, typename=void>
struct foo_impl;
template<typename... Ts>
struct foo_augment {};
template<typename T1>
struct foo_augment<T1> {
  T1 m_t1;
  T1 t1() const { return m_t1; }
  T1 t1() { return m_t1; }
};
template<typename T0, typename... Ts>
struct foo_impl< T0, std::tuple<Ts...>, typename std::enable_if< (sizeof...(Ts)<2) >::type >:
  foo_augment<Ts...>
{
  // use FirstType<Ts...> to get at the second type of your argument pack
  foo_impl( T0 t0, Ts... ts ):
    m_t0(t0), foo_augment<Ts...>(ts...)
  {};
  T0 m_t0;
  T0 t0() { return m_t0; }
  T0 t0() const { return m_t0; }
};
template<typename T0, typename... Ts>
using foo = foo_impl<T0, std::tuple<Ts...>>;

现在,请注意上面有很多样板文件,比您使用的重复代码的数量要多得多。

代替...的混乱,您可以为T1使用"保留值"来表示"不在那里",就像void一样。在这种情况下,可以对构造函数使用这个技巧:

  template<typename... Ts, typename=typename std::enable_if< ((sizeof...(Ts)==0) == (std::is_same<T1, void>::value)) && (sizeof...(Ts)<2) >::type >
  foo_impl( T0 t0, Ts&&... ts ):
    m_t0(t0), foo_augment<Ts...>(std::forward<Ts>(ts)...)
  {};

其中构造函数是可变的,但是SFINAE意味着如果T1void,则Ts...参数包必须为0个元素,如果T1不是void,则必须为1个元素。

(代码尚未编译,但基本设计应该是合理的)。