简体   繁体   中英

Variadic template: inline pattern expansion

C++ variadic templates can use patterns where you can repeat blocks surrounding the variadic argument, like so:

template<typename... Args>
struct MyStruct : seq<pair<Other, Args>...>

MyStruct<X, Y> // expands to seq<pair<Other, X>, pair<Other, Y>>

However, as far as I can tell, all these pattern expansions require (and retain) an enclosing block. I'm looking to expand a pattern inline , like this:

template<typename... Args>
struct MyStruct : seq<???Other, Args???...>

MyStruct<X, Y> // expands to seq<Other, X, Other, Y>

Is there a way to achieve this effect?

Tested with gcc 12:

#include <functional>
#include <tuple>
#include <type_traits>

template<typename ...> class seq;
class Other;

template<typename T> struct tuple_2_seq;

template<typename ...Args>
struct tuple_2_seq<std::tuple<std::reference_wrapper<Args>...>> {

    typedef seq<Args...> type;
};

template<typename ...Args> struct add_other
    : tuple_2_seq<decltype(
                  std::tuple_cat(std::declval<
                      std::tuple<std::reference_wrapper<Other>,
                         std::reference_wrapper<Args>>>()...)
              )> {};

static_assert( std::is_same_v< typename add_other<int, float>::type,
           seq<Other, int, Other, float>>);

So, with all of that in place you'll want to try your luck with

template<typename... Args>
   struct MyStruct : typename add_other<Args...>::type {
 // ...
}

In hindsight using a plain pointer instead of std::reference_wrapper would've also worked, but what's done is done.

If you fancy doing meta-programming yourself, you can accomplish the behaviour with four utility overloads.

Like another answer pointed out, the key is concatenating seq types, so our utilities will do just that. The important thing to understand is that meta-programming is primarily functional in nature, so the tools will be recursion and pattern matching (all during overload resolution). Here are the overloads:

namespace detail {
    auto seq_concat() -> seq<>&;

    template<typename... Args>
    auto seq_concat(seq<Args...>&) -> seq<Args...>&;

    template<typename... Args, typename... Brgs>
    auto seq_concat(seq<Args...>&, seq<Brgs...>&) -> seq<Args..., Brgs...>&;

    template<class Seq, class... Seqs>
    auto seq_concat(Seq& s, Seqs&... ss) -> decltype(seq_concat(s, seq_concat(ss...)));
}

The first three are our "base case". When concatenating zero to two seq s, we lay out explicitly what type we should get, while the final overload is our general case. To concatenate a list of three or more "seq-like" types, is to concentrate the elements in the list tail, then take that result and concatenate it with the list head.

Now we can write the utility you need, because to create a seq<Other, A, Other, B> we may just concatenate seq<Other, A> with seq<Other, B> , and in if we need more types, we can do pack expansion on seq<Other, Args>... and concatenate all of those.

struct Other;
template<typename... Args>
using seq_interleave_with_other = typename std::remove_reference<decltype(detail::seq_concat(std::declval<seq<Other, Args>&>()...))>::type;

We create a pattern std::declval<seq<Other, Args>&>()... to "give us references to objects" we can use in an unevaluated context, then invoke detail::seq_concat(...) on the pattern. The return type is (almost) what we need, because our utilities added references for simplicity of implementing them. A quick trip through std::remove_reference and we are done. To use it, just write:

template<typename... Args>
struct MyStruct : seq_interleave_with_other<Args...>
{};

Here's everything in a live example to play with .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM