3

How do I get the following to put the index of the parameter pack element in the tuple?

template< typename... Ts >
class ClassA {
public:
    ClassA( Ts... ts ) : tup( make_tuple( ts, 0 )... ) {}
    // I would like it to expand to this.
    // ClassA( T0 ts0, T1 ts1 ) : tup( make_tuple( ts0, 0 ), make_tuple(ts1, 1) ) {}
    tuple<tuple<Ts, size_t>...> tup;
};

void main() {
    vector<int> a ={ 2, 4, 5 };
    list<double> b ={ 1.1, 2.2, 3.3 };
    ClassA<vector<int>, list<double>, vector<int>, list<double>> mm( a, b, a, b );
}

Thanks.

Generic Name
  • 1,083
  • 1
  • 12
  • 19
  • Can you tag it with the correct language version? For template heavy things especially, but also just in general, 11 vs 14 vs 17 makes a big difference. – Nir Friedman Mar 01 '18 at 20:44
  • @NirFriedman - Good question; at the moment I've tagged C++11 because the question use varadic templates and tuples (so, at least, C++11). Hoping the OP will precise this point. – max66 Mar 01 '18 at 20:47
  • What you want is to use the [indices trick](https://stackoverflow.com/q/31463388/1593077). And, indeed, @max66 's [answer](https://stackoverflow.com/a/49058344/1593077) applies the trick to your case. – einpoklum Mar 01 '18 at 20:57
  • I'm working with C++17 – Generic Name Mar 03 '18 at 20:50

2 Answers2

7

It seems to me (if you can use at least C++14) a work for a delegating constructor

template <typename ... Ts>
class ClassA
 {
   private:
      template <std::size_t ... Is>
      ClassA (std::index_sequence<Is...> const &, Ts ... ts)
         : tup { std::make_tuple(ts, Is) ... }
       { }

   public:
      ClassA (Ts ... ts)
         : ClassA(std::make_index_sequence<sizeof...(Ts)>{}, ts...)
       { }

      std::tuple<std::tuple<Ts, std::size_t>...> tup;
 };

In C++11 this doesn't works because std::index_sequence and std::make_index_sequence are available only starting from C++14 but it isn't difficult to find (or develop) C++11 substitutes.

max66
  • 65,235
  • 10
  • 71
  • 111
  • This works for me. I actually lke Nirs solution below because it s nice, simple and very readable. However I am not sure if it incurs a slight runtime overhead due to the incrementation of i. – Generic Name Mar 01 '18 at 21:26
  • @user2099460 - yes: the Nir's solution is intriguing. – max66 Mar 01 '18 at 21:39
  • @user2099460 - for the runtime overhead... yes, from a theoretical point of view should be a little (little!) slower, I suppose; but I also suppose that a fairly decent compiler can optimize to avoid this problem. – max66 Mar 01 '18 at 21:42
  • @user2099460 I'm skeptical about runtime overhead. The compiler has all the information it needs to optimize. Also, you're envisioning putting types like `vector` inside this `ClassA`... such types have constructors with many lines of code. Incrementing an integer will be totally irrelevant. – Nir Friedman Mar 01 '18 at 22:18
3

You can avoid making the constructor a template, and index sequence in general, by simply incrementing an integer as part of the pack expansion:

template <typename ... Ts>
class ClassA
 {
   private:
      ClassA (size_t i, Ts ... ts)
         : tup { std::make_tuple(ts, i++) ... }
       { }

   public:
      ClassA (Ts ... ts)
         : ClassA(0, ts...)
       { }

      std::tuple<std::tuple<Ts, size_t>...> tup;
 };

I should note that gcc is currently giving me a warning about lacking a sequence point, but it seems like a false positive (and in any case this can be easily fixed by writing a tiny function and calling it).

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72
  • 1. What if the first parameter of `Ts` is an `int` itself? 2. Are you absolutely sure about the sequence points here? – einpoklum Mar 01 '18 at 20:57
  • @einpoklum Sorry, I don't follow 1. I should probably use `size_t` though not int, technically. As for 2, I don't know about absolutely, but I can't see any behavior from this that is both conforming and incorrect. The only freedom the compiler has here is whether the expression `ts` or `i++` gets evaluated first, for each expansion of the pack. – Nir Friedman Mar 01 '18 at 21:00
  • Edited my (1.) question. 2. Can't the increments start from the end instead of the start of the initialization list? – einpoklum Mar 01 '18 at 21:01
  • @einpoklum Definitely not, initializer lists are left-to-right sequenced (unlike function calls). – Nir Friedman Mar 01 '18 at 21:03
  • In addition; it seems like (not that surprisingly) all variadic pack expansions are sequenced left to right, so even if you change the braces to parens, you're safe: https://stackoverflow.com/questions/22248587/is-the-order-for-variadic-template-pack-expansion-defined-in-the-standard?rq=1. – Nir Friedman Mar 01 '18 at 21:05
  • Even if you're right, I'm tempted to quote [this principle](https://stackoverflow.com/questions/39068546/c-stdtuple-order-of-destruction/39068744#39068744). Can't decide whether what you're doing is intuitively reasonable or not... :-\ – einpoklum Mar 01 '18 at 21:08
  • @einpoklum Meh, incrementing an integer during left to right expansion is a fairly common trick when working with variadics. Index sequences themselves aren't also exactly my definition of "intuitively reasonable". – Nir Friedman Mar 01 '18 at 21:12
  • Intriguing solution... and this avoid also the C++11/C++14 problem. – max66 Mar 01 '18 at 21:46
  • It have to be a false positive if your version implements [DR 1030](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1030) ([GCC 4.9.1+](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51253)) – Nikita Kniazev Mar 01 '18 at 22:21
  • probably should think about making the private constructor a static function, with clang I'm getting it recursively calling the public constructor. Mine is slightly different the parameter pack is a template argument of the constructor not the class. – Conrad Jones Aug 25 '19 at 13:06