10

I would like to convert an std::array to another std::array, multiplying each of its elements by a specific number.

What I have right now obviously doesn't work:

#include <array>
#include <iostream>
#include <utility>

template <class T, size_t... Is, size_t N>
constexpr std::array<T, N> multiply(std::array<T, N> const &src,
                                  std::index_sequence<Is...>) {
    return std::array<T, N>{{src[Is]...}}; // How can I multiply each of src's elements?
}

int main(int argc, char *argv[]) {
    constexpr std::array<int, 3> arr = {1, 2, 3};
    constexpr auto t = multiply(arr, std::make_index_sequence<3>{});
    for (auto &el : t) std::cout << el << std::endl;
    return 0;
}

My question is: how can I iterate over each element at compile time or how can I apply the same function (in my case: multiply by 2) at compile time?

Cœur
  • 37,241
  • 25
  • 195
  • 267
syntagma
  • 23,346
  • 16
  • 78
  • 134

1 Answers1

14

You can do it in the following way:

template<typename T>
constexpr T mult(T const &a, T const &b) { return a * b; }

template <class T, size_t... Is, size_t N>
constexpr std::array<T, N> multiply(std::array<T, N> const &src, 
                                    std::index_sequence<Is...>) {
  return std::array<T, N>{{mult(src[Is], src[Is])...}}; 
}

Live Demo

Or if you want to multiply by a number you can change to:

template<typename T>
constexpr T mult(T const &a, T const &b) { return a * b; }

template <class T, size_t... Is, size_t N>
constexpr std::array<T, N> multiply(std::array<T, N> const &src, 
                                    std::index_sequence<Is...>, T const &mul) {
  return std::array<T, N>{{mult(src[Is], mul)...}}; 
}

Live Demo

As Explained in cppreference:

A pattern followed by an ellipsis, in which the name of at least one parameter pack appears at least once, is expanded into zero or more comma-separated instantiations of the pattern, where the name of the parameter pack is replaced by each of the types from the pack, in order. Pack expansions can only happen in pack expansion contexts. These essentially are :

  • braced initialization
  • initializer lists
  • aggregate initializations
  • function calls
  • array initializations

Edit:

As T.C. pointed in the comments you can also do it as simple as:

template <class T, size_t... Is, size_t N>
constexpr std::array<T, N> multiply(std::array<T, N> const &src, std::index_sequence<Is...>, T const &mul) {
  return std::array<T, N>{{(src[Is] * mul)...}}; 
}

Live Demo

101010
  • 41,839
  • 11
  • 94
  • 168
  • How does the `{{mult(src[Is], src[Is])...}` part work exactly? The `...` is a bit confusing in this case. – syntagma Dec 01 '15 at 22:25
  • @REACHUS This is one of the ways you can expand packs. Wait I'll try to explain. – 101010 Dec 01 '15 at 22:28
  • Thanks, one more question: would there be any way to replace `mult` with a lambda? I have tried it but it looks like it won't be possible, since lambdas cannot be `constexpr`s. – syntagma Dec 01 '15 at 22:36
  • `constexpr` lambdas I think will be allowed as from C++17. – 101010 Dec 01 '15 at 22:37
  • 4
    `{{ (src[Is] * mul) ...}}` or even `{{ src[Is] * mul ...}}` works just fine. There's no point in using an extra function. – T.C. Dec 01 '15 at 22:49
  • @T.C. isn't that a C++17 addition? – M.M Dec 02 '15 at 00:46
  • @M.M No, bog-standard pack expansion in a *braced-init-list*. It's not a fold expression. – T.C. Dec 02 '15 at 02:11
  • OK, I didn't actually read the list of "pack expansion contexts" before. That list you have is all *initializer-list*s, which is like one out of ten cases (not counting fold expressions and `sizeof...`) described in the standard. – T.C. Dec 02 '15 at 03:24