4

I have a ctor declared like this:

template<typename... Coords>
MyClass<T>(vector<T> values, Coords... coords) { /* code */ }

I want it to look like this:

template<typename... Coords>
MyClass<T>(Coords... coords, vector<T> values) { /* code */ }

but the standard requires the variadic argument to be the last. If I write something like

template<typename... Args>
MyClass<T>(Args... coordsThenValues) { /* code */ }

how would I split coordsThenValues into an all-but-last parameter pack Coords... coords and the last parameter vector<T> values?

jcai
  • 3,448
  • 3
  • 21
  • 36

1 Answers1

6

Do you like tuples?

How do you like forward as tuple?

struct foo {
  template<class...Ts>
  foo(Ts&&...ts):
    foo(
      magic<0>{}, // sent it to the right ctor
      std::index_sequence< sizeof...(ts)-1 >{}, // the last shall be first
      std::make_index_sequence<sizeof...(ts)-1>{}, // the first shall be last
      std::forward_as_tuple(std::forward<Ts>(ts)...) // bundled args
    )
  {}
private:
  template<size_t>
  struct magic {};
  template<size_t...I0s, size_t...I1s, class...Ts>
  foo(
    magic<0>, // tag
    std::index_sequence<I0s...>, // first args
    std::index_sequence<I1s...>, // last args
    std::tuple<Ts...> args // all args
  ):
    foo(
      magic<1>{}, // dispatch to another tagged ctor
      std::get<I0s>(std::move(args))..., // get first args
      std::get<I1s>(std::move(args))... // and last args
    )
  {}
  // this ctor gets the args in an easier to understand order:
  template<class...Coords>
  foo(magic<1>, std::vector<T> values, Coords...coords) {
  }
};

here the public ctor packs up the arguments into a tuple of references (possibly l, possibly r). It also gets two sets of indexes.

It then sends it to magic<0> ctor, which shuffles the arguments around so that the last one is first (assuming the indexes are right).

The magic<1> ctor gets the vector first, then the coords.

Basically I shuffled the arguments around so the last one became first.

magic just exists to make me not have to think much about overload resolution, and make which ctor I'm forwarding to explicit. It would probably work without it, but I like tagging when I'm doing something crazy with ctor forwarding.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Should be `std::forward_as_tuple(std::forward(ts)...)`, other than that this is perfect. – jcai Jul 06 '15 at 23:16
  • I modified your code somewhat. The main problem is: `foo(Ts&&...ts)` catches *everything*. If the `magic<0>` ctor tries to pass to the `magic<1>` ctor with invalid args, the passing will fail and it will actually call `foo(Ts&&...ts)` causing an infinite compiler loop. So instead of `template`, I wrote `template 1 && !(is_magic>::value))>::type>`. (I moved `magic` outside the class definition and wrote a simple `is_magic` and `NthTypeOf`). – jcai Jul 07 '15 at 20:28
  • @arcinde One of the points of `magic` is that nobody else (besides the class) has the right to create it. Which that subverts. – Yakk - Adam Nevraumont Jul 07 '15 at 20:34
  • It's not so bad if the constructors that take it are private? `magic` is not exposed in any way. The only reason I did it is because I was running into annoying issues with templated structs inside a templated class. – jcai Jul 07 '15 at 20:37
  • 1
    `template static std::true_type is_magic_f( magic ) { return {}; } template static std::false_type is_magic_f( T ) { return {}; } template using is_magic = decltype( is_magic_f( std::declval() ) );` ;) – Yakk - Adam Nevraumont Jul 07 '15 at 20:38
  • Hmm, in the `magic<1>{}` constructor call of foo, I spot `args` being `std::move`d twice; won't one of these invalidate `args` for the other parameter being evaluated? Or is the real moving only really being done on the `std::get`ed value? – rubenvb Apr 30 '17 at 22:26
  • @rubenvb `std::move` does not move, `std::forward` does not forward. Both are just casts-to-rvalue, any moving happens within the consuming function. The behaviour of `std::get` is like a member access; when it moves, it only moves that one element being gotten. If the indexes overlapped, then there would be a problem. – Yakk - Adam Nevraumont Apr 30 '17 at 22:56
  • @Yakk, right, so those are really only "move"ing the parameters extracted by get. (note the quotes, I know std::move doesn't move, but I do know the stuff around the move can actually move stuff that is std::move'd). And now I've given myself a headache. Perhaps std::move'ing some aspirin somewhere might help with that. – rubenvb Apr 30 '17 at 23:00
  • I think there is still one issue in this implementation. `std::tuple ` won't work as you (probably) expect.`Ts&&` won't be forwarding references, they will be regular rvalue references. You can see this is a problem if you try to use `const char*` instead of Coords: https://godbolt.org/z/8K16xKehT The solution is to use a template parameter for tuple instead of `std::tuple`, as suggested here: https://stackoverflow.com/questions/28450892/perfect-forwarding-and-stdtuple After this change, it works fine with `const char*`: https://godbolt.org/z/fPGq8PM6c – Igor Aug 03 '21 at 12:18
  • 1
    @igor or just remove the `&&` there – Yakk - Adam Nevraumont Aug 03 '21 at 12:56