7

I want to have two similar templates, one with 1 parameter and other with 2 parameters:

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; }
};

Notice the code duplication for all the T1-related stuff. How can I avoid this?

7cows
  • 4,974
  • 6
  • 25
  • 30
  • Your code as is has intrinsic ambiguity beyond the problem you're trying to solve: For single parameter template the compiler won't be able to resolve the template to use. E.g.: http://ideone.com/7wcB6Z – SomeWittyUsername Jun 07 '13 at 13:52
  • @icepack What do you mean? Doesn't your code fail, as expected, because the class doesn't have a default constructor? – 7cows Jun 07 '13 at 15:38

5 Answers5

7

The only solution to this is to have as much code as possible in a base class. For example:

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) {}
};
2

This is a very general sort of question.

In this case, you could put the T1-related stuff in a base class. In general, you should avoid the one-two-many problem. What you're doing here could also be achieved by std::tuple, which is also part of the Boost library.

For what it's worth, tuple works by composing any number of base classes.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
2

Looking closely at your code, you are reimplementing std::tuple. Exchange the t1 and t2 methods for the free function std::get<N> and you have everything (and maybe more) std::tuple gives you. For convenience, if it has to be a method, consider this:

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

For the //!: Of course you could make that constructor a (variadic) template and just forward the arguments to the tuple. Oh, and the accessor could/should be overloaded for const and nonconst and return appropiate references to the tuple elements...

But seriously, it's not worth the sweat. Just use plain std::tuple. Except of course you oversimplified the problem and you are doing something different than you told us.

Arne Mertz
  • 24,171
  • 3
  • 51
  • 90
0

Inheritance would solve your problem. Define a one-parameter base class that provides the T1 stuff and make the two-parameter version inherit that.

rubenvb
  • 74,642
  • 33
  • 187
  • 332
0

There are three numbers: 0, 1 and infinity.

Oh, and counting starts at 0, not 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...>>;

Now, note that there is lots of boilerplate above, a fair amount more than the amount of duplicated code you used.

Instead of the ... mess, you could use a "reserved value" for T1 to indicate "not there", like void. In that case, you can use this trick with the constructor:

  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)...)
  {};

where the constructor is variardic, but SFINAE means that the Ts... parameter pack must be 0 elements iff T1 is void, and must be 1 element iff T1 is not void.

(Code not compiled yet, but basic design should be sound).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524