There are three numbers: 0, 1 and infinity.
Oh, and the countdown 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 notice that there are many patterns above, a fair amount more than the amount of duplicated code that you used.
Instead of clutter ... you can use the "reserved value" for T1 to indicate no, for example void . In this 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 a variable, but SFINAE means that the package of parameters Ts... must be 0 elements iff T1 is void and must be 1 element iff T1 not void .
(The code has not yet been compiled, but the basic design should be sound).
Yakk
source share