d seems fixed, so it is not really variadic.
You can then have specializations (up to a certain limit)
template <std::size_t d> struct S; template <> struct S<0> { void upd(int u1, T val){ u1 += n; while(u1){ t[u1].upd(val); u1 >>= 1; } } }; template <> struct S<1> { template <typename T1, typename T2> void upd(int u1, T1 t1, T2 t2, T val){ u1 += n; while(u1){ t[u1].upd(T1, t2, val); u1 >>= 1; } } }; //... // struct S<2> // ... // void upd(int u1, T1 t1, T2 t2, T3 t3, T4 t4, T val){ // ...
If the types T1, TN are fixed (assuming SomeType), one possibility is to use std::index_sequence instead of d
template <std::size_t, typename T> using always_t = T; template <typename Seq> struct S_impl; template <std::size_t... Is> struct S_impl<std::index_sequence<Is...>> { void upd(int u1, always_t<Is, SomeType>... args, T val){ u1 += n; while(u1) { t[u1].upd(args..., val); u1 >>= 1; } } }; template <std::size_t d> using S = S_impl<std::make_index_sequence<d>>;
Else, you can use tuple or reordering argument for implementation, and provide wrapper for the interface:
template<std::size_t... Is, typename Tuple> auto extract(std::index_sequence<Is...>, Tuple t) { return std::tie(std::get<Is>(t)...); } template <typename ...Ts, enable_if_t<sizeof...(Ts) == 2 * (d - 1), int> = 0> void upd_impl(int u1, std::tuple<Ts...> tup, T val) { u1 += n; while(u1){ std::apply([&](auto&&... args){ t[u1].upd(args..., val); u1 >>= 1; }, tup } } template <typename ...Ts, enable_if_t<sizeof...(Ts) == 1 + 2 * (d - 1), int> = 0> void upd(int u1, Ts... args) { upd_impl(u1, extract(std::make_index_sequence<sizeof...(Ts) - 1>(), std::tie(args...)), std::get<sizeof...(Ts) - 1>(std::tie(args...))); }
void func(int l,std::tuple<args....> arg,int r)?func.dis given?