Multiplying each element of an std::array at compile time
Asked Answered
S

1

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?

Superstructure answered 1/12, 2015 at 22:14 Comment(0)
B
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

Bartel answered 1/12, 2015 at 22:23 Comment(8)
How does the {{mult(src[Is], src[Is])...} part work exactly? The ... is a bit confusing in this case.Superstructure
@REACHUS This is one of the ways you can expand packs. Wait I'll try to explain.Bartel
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 constexprs.Superstructure
constexpr lambdas I think will be allowed as from C++17.Bartel
{{ (src[Is] * mul) ...}} or even {{ src[Is] * mul ...}} works just fine. There's no point in using an extra function.Trepidation
@Trepidation isn't that a C++17 addition?Wakefield
@Wakefield No, bog-standard pack expansion in a braced-init-list. It's not a fold expression.Trepidation
OK, I didn't actually read the list of "pack expansion contexts" before. That list you have is all initializer-lists, which is like one out of ten cases (not counting fold expressions and sizeof...) described in the standard.Trepidation

© 2022 - 2024 — McMap. All rights reserved.