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;
}
>
> +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