c++templatesparameter-packexpression-templates

Can I generalize this set of functions into one that takes a variable length argument list (e.g. by using a parameter pack)


I want to know if there is a way to generalize this set of functions into one that takes an arbitrary number of arguments. The specifics of Var and LetN shouldn't be needed here. (This is from some expression template building code. LetN creates a local lexical environment using the argument expressions, and the indices of the arguments wrapped in Var expressions.) The thing I don't know how to do is write an expression that zips an index sequence with the argument output types into instances of Var.

template <typename Fun, typename A>
auto let(Fun fun, A a) {
    auto var0 = Var<0, get_out_type_t<A>>();
    return LetN(fun(var0), a);
}

template <typename Fun, typename A, typename B>
auto let(Fun fun, A a, B b) {
    auto var0 = Var<0, get_out_type_t<A>>();
    auto var1 = Var<1, get_out_type_t<B>>();
    return LetN(fun(var0, var1), a, b);
}

template <typename Fun, typename A, typename B, typename C>
auto let(Fun fun, A a, B b, C c) {
    auto var0 = Var<0, get_out_type_t<A>>();
    auto var1 = Var<1, get_out_type_t<B>>();
    auto var2 = Var<2, get_out_type_t<C>>();
    return LetN(fun(var0, var1, var2), a, b, c);
}
// etc.. for other arities.

Here is my failed attempt:

template <typename Fun, typename... Args>
auto let2(Fun fun, Args... args) {
    using ArgsTuple = std::tuple<Args...>;
    auto g = [&](auto... indices){
        return std::make_tuple(Var<indices, typename std::tuple_element<indices, ArgsTuple>::OutType>()...);
    };
    auto vars = std::apply(g, std::index_sequence_for<Args...>());
    return LetN(fun(vars), args...);
}

My attempt generates some compile errors regarding the integer_sequence that I don't really understand.

[...]/usr/include/c++/v1/tuple:1528:52: error: implicit instantiation of undefined template 'std::tuple_size<std::integer_sequence<unsigned long, 0, 1>>'
_LIBCPP_INLINE_VAR constexpr size_t tuple_size_v = tuple_size<_Tp>::value;

[...]/usr/include/c++/v1/tuple:1548:39: error: non-type template argument is not a constant expression
        typename __make_tuple_indices<tuple_size_v<remove_reference_t<_Tuple>>>::type{})

The next question is that even if I can do this, should I? Will it increase compile times to execute all this parameter pack code at compile time rather than just dispatching to a set of fixed arity function overloads?


Solution

  • My attempt generates some compile errors regarding the integer_sequence that I don't really understand.

    The second argument of std::apply expects a tuple, index_sequence is not a tuple, so tuple_size<index_sequence> is ill-formed.

    The next question is that even if I can do this, should I?

    With C++20, you can simply do

    template <typename Fun, typename... Args>
    auto let2(Fun fun, Args... args) {
      return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
        return LetN(fun(Var<Is, get_out_type_t<Args>>()...), args...);
      }(std::index_sequence_for<Args...>{});
    }