Как избежать дублирования кода в специализированном шаблоне

Я хочу иметь два похожих шаблона, один с 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. Как я могу этого избежать?


person 7cows    schedule 07.06.2013    source источник
comment
Ваш код как есть имеет внутреннюю неоднозначность помимо проблемы, которую вы пытаетесь решить: для шаблона с одним параметром компилятор не сможет разрешить шаблон для использования. Например: ideone.com/7wcB6Z   -  person SomeWittyUsername    schedule 07.06.2013
comment
@icepack Что ты имеешь в виду? Разве ваш код не дает сбой, как и ожидалось, потому что у класса нет конструктора по умолчанию?   -  person 7cows    schedule 07.06.2013


Ответы (5)


Единственное решение — иметь как можно больше кода в базовом классе. Например:

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) {}
};
person Community    schedule 07.06.2013
comment
Какое отношение имеет явное отношение к рассматриваемому вопросу? - person 7cows; 07.06.2013
comment
@7cows: Хорошая практика. См. stackoverflow.com /вопросы/121162/ - person ; 07.06.2013
comment
и пока вы этим занимаетесь, можно было бы добавить некоторые вещи, связанные с константной правильностью. - person TemplateRex; 07.06.2013

Это очень общий вопрос.

В этом случае вы можете поместить все, что связано с T1, в базовый класс. В общем, вам следует избегать проблемы «один-два-много». То, что вы делаете здесь, также может быть достигнуто с помощью std::tuple, который также является частью библиотеки Boost.

Что бы это ни стоило, tuple работает, составляя любое количество базовых классов.

person Potatoswatter    schedule 07.06.2013

Присмотревшись к своему коду, вы заново реализуете std::tuple. Обменяйте методы t1 и t2 на бесплатную функцию 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. За исключением, конечно, того, что вы упростили проблему и делаете что-то другое, чем вы сказали нам.

person Arne Mertz    schedule 07.06.2013

Наследование решит вашу проблему. Определите базовый класс с одним параметром, который предоставляет материал T1, и сделайте так, чтобы версия с двумя параметрами наследовала его.

person rubenvb    schedule 07.06.2013

Есть три числа: 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 означает, что пакет параметров Ts... должен состоять из 0 элементов, если T1 равен void, и должен состоять из 1 элемента, если T1 не равен void.

(Код еще не скомпилирован, но базовый дизайн должен быть здравым).

person Yakk - Adam Nevraumont    schedule 07.06.2013