CTAD with variable number of template arguments

118 views Asked by At

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?

2

There are 2 answers

0
Ted Lyngmo On BEST ANSWER

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 ScopePairs 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>...>;

Demo

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

Demo

0
Miles Budnek On

I wasn't able to come up with a direct deduction guide, but was able to make it work using a type wrapper type and an extra ScopeSet partial specialization:

template <typename... Ts>
struct TypeWrapper {};

template <typename... Ts>
struct GetType
{
    template <std::size_t I>
    using Type = std::tuple_element_t<I, std::tuple<Ts...>>;
};

template <typename... Ts, std::size_t... Is>
auto types_skip_odd(std::index_sequence<Is...>)
{
    return TypeWrapper<std::remove_reference_t<typename GetType<Ts...>::template Type<Is * 2>>...>();
}

template <typename... Ts>
auto even_types()
{
    return types_skip_odd<Ts...>(std::make_index_sequence<sizeof...(Ts) / 2>());
}

template <typename... Ts>
struct ScopeSet<TypeWrapper<Ts...>> : ScopeSet<Ts...>
{
    using ScopeSet<Ts...>::ScopeSet;
};

template <typename... Ts>
ScopeSet(Ts...) -> ScopeSet<decltype(even_types<Ts...>())>;

Demo

The basic concept is to have ScopeSet(int&, int, double&, double) deduce to ScopeSet<TypeWrapper<int, double>> and make ScopeSet<TypeWrapper<Ts...>> inherit from ScopeSet<Ts...>.

This way, auto _ = ScopeSet( i, 10, d, 20. ); will deduce to ScopeSet<TypeWrapper<int, double>>, which will then behave exactly like ScopeSet<int, double>.