C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Template parameter packs followed by more types

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Wed, 22 May 2024 15:19:18 +0100
On Wed, May 22, 2024 at 1:03 PM Jarrad Waterloo wrote:
>
> +1
>
> I have a similar need for wanting to add default initialized
> arguments at the end of my variadic templates such as
> source_location.


I was able to code a workaround. So you start off with:

    template<typename... A, typename B>
    auto Func(A&&... a, B &&b)
    {
        return (a + ...) + b;
    }

Rename this function to "Func_impl" and re-arrange the parameters:

    template<typename B, typename... A>
    auto Func_impl(B &&b, A&&... a)
    {
        return (a + ...) + b;
    }

So now we write "Func" as follows:

    template<typename... AandB>
    auto Func(AandB&&... a_and_b)
    {
        // We need to invoke 'Func_impl' with A and B reversed
    }

I create a tuple with references to all the types from "AandB" in it,
and then I move the last element to the beginning, and then I invoke
"Func" with this new re-ordered tuple. Here it is tested and working
on GodBolt:

    https://godbolt.org/z/hjnTYfo5v

And here it is copy-pasted:

#include <cstddef> // size_t
#include <tuple> // make_tuple, tuple, tuple_size
#include <type_traits> // is_reference
#include <utility> // forward, index_sequence, make_index_sequence, move

// The following function concatenates two index_sequences,
// Example Input: index_sequence<5>, index_sequence<0,1,2,3,4>
// Example Output: index_sequence<5,0,1,2,3,4>
template <std::size_t... As, std::size_t... Bs>
consteval auto ConcatSeq(std::index_sequence<As...>,std::index_sequence<Bs...>)
{
    return std::index_sequence<As...,Bs...>{};
}

// The following template type is an index_sequence
// but with the last element moved to the beginning
template<std::size_t n>
using Sequence =
decltype(ConcatSeq(std::index_sequence<n-1u>{},std::make_index_sequence<n-1u>{}));

// Helper function to re-order the tuple
template<typename Tuple, std::size_t... Indices> requires
(!std::is_reference_v<Tuple>) // Rvalue only
constexpr auto ReorderTuple_impl(Tuple &&t, std::index_sequence<Indices...>)
{
    // The tuple is just a tuple full of references,
    // but some may be Lvalue and some may be Rvalue,
    // so we use decltype here to keep the correct type
    return std::tuple< decltype(std::get<Indices>(std::move(t)))... >{
        std::get<Indices>(std::move(t))...
    };
}

// Main function to re-order the tuple
template<typename Tuple> requires (!std::is_reference_v<Tuple>) // Rvalue only
constexpr auto ReorderTuple(Tuple &&t) // should require Tuple is a
specialisation of std::tuple
{
    return ReorderTuple_impl(
        std::move(t),
        Sequence< std::tuple_size_v<Tuple> >{});
}

// Here is our original function re-written
// with the order of parameters reversed
// -- we don't expose this to the programmer
// who is using our SDK
template<typename B, typename... A>
constexpr auto Func_impl(B &&b, A&&... a)
{
    return (a + ...) + b;
}

// We want to invoke the above function
// with a tuple, but we can't use std::apply,
// because we don't know all the types.
// So instead we use the following two helper functions:
template<typename Tuple, std::size_t... Indices> requires
(!std::is_reference_v<Tuple>) // Rvalue only
constexpr decltype(auto) CallWithTuple_impl(Tuple &&t,
std::index_sequence<Indices...>)
{
    return Func_impl( std::get<Indices>(std::move(t))... );
}

template<typename... Ts> requires (std::is_reference_v<Ts> && ...) //
only accept a tuple full of references
constexpr decltype(auto) CallWithTuple(std::tuple<Ts...> &&t) // not a
forwarding ref - always an Rvalue
{
    return CallWithTuple_impl( std::move(t), std::make_index_sequence<
sizeof...(Ts) >{} );
}

// The following function is exposed to
// the programmer who is using our SDK,
// who wants to use the original
// order of function arguments
template<typename... AandB>
constexpr auto Func(AandB&&... a_and_b)
{
    return
      CallWithTuple(
        ReorderTuple( std::tuple<AandB&&...>{
static_cast<AandB&&>(a_and_b)... } ) );
}

// ============================== Now let's test it out

#include <iostream> // cout, endl
#include <string> // ""s

int main()
{
    constexpr auto value = Func(1,6.7f); // woohoo consteval works!

    using namespace std::string_literals;
    std::cout << Func("Hello "s, "World"s) << std::endl;
}

Received on 2024-05-22 14:19:32