Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Two Type Variadic Expansion

Tags:

c++

c++17

How does one do two type variadic expansion? Here is what I am trying to achieve:

#include <vector>
#include <iostream>

class test
{
public:
    std::vector< std::pair< float, int > > vec;
    template<typename... T1, typename... T2>
    test( T1... one, T2... two )
    {
        (
            [&](float first, int second)
            {
                vec.emplace_back( std::pair< float, int >( first, second ) );
                std::cout << first << ", " << second << std::endl;
            }( one, two ),
            ...
        );
    }
};

int main()
{
    test t
    {
        1.f,
        1,
        2.f,
        2,
        3.f,
        3
    };

    return 0;
}

test must be initialized exactly like in main. I'd like usage in the constructor of test to remain similar.

Here is a working concept with va_list. Unfortunately, I need to pass a count for the parameters or I need to pass a magic-number terminator (I opted for the magic-number terminator).

#include <cstdarg>
#include <iostream>
#include <vector>

constexpr int END_OBJECT = 890123; // magic number

class test {
 public:
  std::vector<std::pair<int, double>> vec;

  enum { is_name, is_data, is_max };

  test(char ch, ...) {
    std::pair<int, double> buf;

    va_list b;
    va_start(b, ch);
    for (int i = 0;; i++) {
      auto is = i % is_max;

      if (is == is_name) {
        if ( (buf.first = va_arg(b, int)) == END_OBJECT )
            break;
      } else if (is == is_data) {
        buf.second = va_arg(b, double);
        vec.emplace_back(buf);
      }
    }
    va_end(b);
    std::cout << ch << std::endl;
    for (auto &x : vec)
      std::cout << '\t' << x.first << ", " << x.second << std::endl;
  }
};

int main() {
  test t
  {
    'x',
    1,
    2.0,
    3,
    4.0,
    5,
    6.0,
    END_OBJECT
  };

  return 0;
}

I'd like a more modern version of this using pack-expansion.

like image 701
nowi Avatar asked Nov 30 '25 07:11

nowi


1 Answers

It's funny how this is basically FizzBuzz with template arguments and it's actually a nice challenge.

The easiest way in C++14 I could come up with is to use std::index_sequence. https://godbolt.org/z/dm3F9u

#include <vector>
#include <utility>
#include <tuple>

template <typename... TArgs, size_t... Is>
std::vector<std::pair<float, int>> pair_off(std::tuple<TArgs...> args, std::index_sequence<Is...>) {
    return std::vector<std::pair<float, int>> { std::make_pair(std::get<(Is << 1)>(args), std::get<((Is << 1) + 1)>(args))... };
}


template <typename... TArgs>
std::vector<std::pair<float, int>> pair_off(TArgs&&... args) {
    return pair_off(std::forward_as_tuple(std::forward<TArgs>(args)...), std::make_index_sequence<(sizeof...(TArgs) >> 1)>{});
}

std::vector<std::pair<float, int>> test() {
    return pair_off(1.1f, 1, 2.2f, 2, 3.3f, 3);
}

Basically, first you pack the arguments into a tuple. Then you make an index sequence half the size of the argument list. Then you expand that index sequence, passing it into std::get.

What that does is the template equivalent of:

for (int i=0; i<list.size()/2; i++) { 
    output.push_back( std::make_pair(list[i*2], list[i*2+1]) ); 
}
like image 110
parktomatomi Avatar answered Dec 02 '25 22:12

parktomatomi



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!