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).