3

Possible Duplicate:
Convert std::tuple to std::array C++11

Suppose you have:

template<class T,int N>
struct A {
  A(const B& b): /* what comes here */ {}

  std::array<T,N> F;
};

I need each element of F[] to be constructed with the argument of the constructor, in above case b. This is tricky because the argument might not be of a type that can be a compile-time constant, like int etc.

This is different to Is it possible to construct the elements of a member array depending on an integral template parameter? since here a user-defined struct is used and thus we need run-time copies of it.

Community
  • 1
  • 1
ritter
  • 7,447
  • 7
  • 51
  • 84

2 Answers2

8

The indices trick can be applied here too, you just need to transform it a bit:

template <std::size_t... Is>
struct indices {};

template <std::size_t N, std::size_t... Is>
struct build_indices
  : build_indices<N-1, N-1, Is...> {};

template <std::size_t... Is>
struct build_indices<0, Is...> : indices<Is...> {};

template<class T,int N>
struct A {
  template<std::size_t... Is>
  A(const B& b, indices<Is...>) : F{{(void(Is),b)...}} {}
  A(const B& b) : A(b, build_indices<N>{}) {}

  std::array<T,N> F;
};

Live example.

We basically ignore the values of the indices and only use the pack itself to perform the expansion, i.e. only the size of the pack is of interest to us. Reusing indices for this might seem like abuse, since we're not interested in the actual values, but I think reusing the machinery here is fine. Any other construct to create an N-element pack would look the same, except that the pack would most likely just contain zeros.

Xeo
  • 129,499
  • 52
  • 291
  • 397
  • Very cool! What (and why is it needed) is `void(Is)` ? – ritter Oct 11 '12 at 18:39
  • 2
    I had to stare at the code a while to figure out. `(Is)...` will be each of the `N` parameters in order. `(void(Is))...` will be `N` voids. `(void(Is),B)` is a `B`. So `(void(Is),B)..` is `N` copes of the `B` object! :D It's brilliant! – Mooing Duck Oct 11 '12 at 18:43
  • @Frank: In C++, you can overload the `operator,` (comma operator, which I use in the parens) for user-defined types. However, you can force the built-in one to be used by inserting a `void()` "value" as the either side of the comma. It could've been written as `(Is, void(), b)...`, but then you get warnings about the left-hand-side of the comma operator having no effect. With `void(Is)`, the value is explicitly discarded (you can "construct" a `void` "value" from anything) and the warning disappears. – Xeo Oct 11 '12 at 18:43
  • But wouldn't `(Is, void(), b)...` be expanded to `(0, b)(1, b)...` (`void` vanishs) which is not what we want. `(void(Is),b)...` seems really the only choice. – ritter Oct 11 '12 at 18:48
  • @Frank: It would expand to `(0, void(), b), (1, void(), b), ...`, which is basically equivalent to `(0, b), (1, b), ...)` but with the built-in meaning of `,`. – Xeo Oct 11 '12 at 18:48
  • I wrote the equivalent of `(void)Is` to avoid that warning, I think that's a bit easier to recognize as discarded-value expression. But `void(Is)` looks less C-like. – Cubbi Oct 11 '12 at 19:08
  • @Xeo Can you have a look at this one: http://stackoverflow.com/questions/12857047/could-not-convert-from-brace-enclosed-initializer-list It seems when moving the whole thing to a base class the conversion from `(0,b)` no longer works. – ritter Oct 12 '12 at 10:28
  • @Frank: Is something still missing from my answer? If yes, please tell me. :) – Xeo Oct 23 '12 at 12:04
  • @Xeo Nothing missing. Just me accepting it ;) – ritter Oct 23 '12 at 14:49
0

The build_indicies trick as shown by Xeo is clever and probably the best option if your compiler supports it.

Another option might be to allow the elements to be default constructed, then to destroy them and reconstruct them with placment new/uninitialized_fill:

template<class T,int N>
struct A {
  A(const B& b) {
      for (size_t i=0;i<N;++i) {
          F[i].~T();
          new (&F[i]) T(b);
      }
  }

  std::array<T,N> F;
};

You could also use storage that wouldn't be initialized normally, to avoid the default construction:

template<class T,int N>
struct A {    
  typedef std::array<T,N> array_type;
  std::aligned_storage<sizeof(array_type),alignof(array_type)>::type storage;

  array_type &F() {
    return reinterpret_cast<array_type&>(storage);
  }

  A(const B& b) {
    // placement new/uninitialized_fill
  }
};
bames53
  • 86,085
  • 15
  • 179
  • 244
  • You might also use `boost::optional` to avoid the default-construction altogether. – Xeo Oct 11 '12 at 20:45