For unit testing, I wrote a class ScopeSet
which changes the values of variables in the current scope, and at the end of the scope the original values are set back.
Usage example:
int i = 1;
double d = 2.;
{
auto _ = ScopeSet( i, 10, d, 20. );
assert( i == 10 );
assert( d == 20. );
}
assert( i == 1 );
assert( d == 2. );
I have finished the class, except for the deduction guides.
ScopeSet
takes a variable number of variable-value
pairs.
So far I have written the deduction guides by hand for up to three pairs
template< typename T0, typename U0 > ScopeSet( T0 &, U0 ) -> ScopeSet< T0 >;
template< typename T0, typename U0, typename T1, typename U1 > ScopeSet( T0 &, U0, T1 &, U1 ) -> ScopeSet< T0, T1 >;
template< typename T0, typename U0, typename T1, typename U1, typename T2, typename U2 > ScopeSet( T0 &, U0, T1 &, U1, T2 &, U2 ) -> ScopeSet< T0, T1, T2 >;
My question is, how can I write a deduction guide which works for a variable number of pairs?
You could start with a class to store a reference to a single variable, that assigns a new value to the variable at construction and restores the old value on destruction:
template <class T>
struct ScopePair {
template <class V>
ScopePair(T& var, V&& val) : variable(var), saved(std::move(var)) {
variable = std::forward<V>(val);
}
~ScopePair() { variable = std::move(saved); }
T& variable;
T saved;
};
template <class T, class U>
ScopePair(T, U) -> ScopePair<std::remove_cvref_t<T>>;
This ScopePair
is usable as-is if you only need to handle a single variable.
The ScopeSet
can then consist of any number of such ScopePair
s by recursively inheriting from a ScopeSet
where one pair of arguments are picked off at a time:
template <class... Ts> struct ScopeSet {};
template <class T, class V, class... Rs>
struct ScopeSet<T, V, Rs...> : ScopeSet<Rs...> {
template <class VV, class... RRs>
ScopeSet(T& t, VV&& v, RRs&&... rest)
: ScopeSet<Rs...>(std::forward<RRs>(rest)...),
pair(t, std::forward<VV>(v)) {}
ScopePair<T> pair;
};
template <class... Ts>
ScopeSet(Ts...) -> ScopeSet<std::remove_cvref_t<Ts>...>;
The above makes ScopeSet( i, 10, d, 20. );
into a ScopeSet<int, int, double, double>
which is much easier than to shave off every other type.
If you absolutely must have a one int
and one double
you could add a specialization for ScopeSet<ScopeSetTypes<Ts...>>
that inherits from ScopeSet<Ts...>
. Using the deduction guide will then make ScopeSet( i, 10, d, 20. );
into a ScopeSet<ScopeSetTypes<int, double>>
:
template<class...Ts> struct ScopeSetTypes{};
template <class... Ts>
struct ScopeSet<ScopeSetTypes<Ts...>> : ScopeSet<Ts...> {
using ScopeSet<Ts...>::ScopeSet;
};
// The deduction guide:
template <class... Ts>
ScopeSet(Ts...) -> ScopeSet<
decltype([]<std::size_t... Is>(std::index_sequence<Is...>)
-> ScopeSetTypes<std::tuple_element_t<
Is * 2, std::tuple<std::remove_cvref_t<Ts>...>>...> {
}(std::make_index_sequence<sizeof...(Ts) / 2>()))>;