C++ constexpr : Compute a std array at compile time
Asked Answered
I

2

9

I want to convert an "array" of bool to a integer sequence. So I need to compute an std::array at compile time.

Here is my code

#include <array>

template<typename InputIt, typename T >
inline constexpr typename std::iterator_traits<InputIt>::difference_type
count( InputIt first, InputIt last, const T &value ) {
    typename std::iterator_traits<InputIt>::difference_type ret = 0;
        for (; first != last; ++first) {
            if (*first == value) {
                ret++;
            }
        }
        return ret;
}

template<bool ..._values>
struct keep_value {
    static constexpr std::size_t numberOfValues = sizeof...(_values);
    static constexpr bool values[] = {_values...};
    static constexpr std::size_t numberToKeep = count(values, values + numberOfValues, true);

    static constexpr std::array<std::size_t, numberToKeep> computeIndices() {
        std::array<std::size_t, numberToKeep> array{};
        auto it = array.begin();
        for(std::size_t i{0}; i < numberOfValues; ++i)
            if(values[i] == true)
                *it++ = i;

        return array;
    }

    static constexpr std::array<std::size_t, numberToKeep> indices = computeIndices();

    template<typename Indices = std::make_index_sequence<numberToKeep>>
    struct as_index_sequence{};

    template<std::size_t ...Is>
    struct as_index_sequence<std::index_sequence<Is...>> : std::index_sequence<indices[Is]...>{};
};

int main() {
    keep_value<false, true, true>::template as_index_sequence<>{}; // Should return the sequence 1 2
}

I get an error for the line that call the computeIndices function. Is this code c++14 correct? Is it possible to do otherwise? I am using MSVC and I get this error : expression did not evaluate to a constant

Instrument answered 4/3, 2018 at 15:50 Comment(3)
Clang seems to give the same error when using c++14, however, compiles is with c++17Infinitude
Hmm, maybe the functions are not constexpr in C++14, but are in C++17. The same for std::count that will be constexpr in C++20Instrument
Indeed, see my answerInfinitude
I
7

This code looks correct and works when compiled as C++17. It uses std::array::begin, which only has been made constexpr in C++17.

A better compilation error can be achieved when using clang, which states:

<source>:23:25: note: non-constexpr function 'begin' cannot be used in a constant expression
    auto it = array.begin();
Infinitude answered 4/3, 2018 at 16:5 Comment(0)
D
2

Is it possible to do otherwise?

About correctness answered JVApen (+1).

A possible alternative is avoid std::array at all and construct the index sequence in a recursive way using template specialization

The following is a full compilable example

#include <utility>
#include <type_traits>

template <typename, std::size_t, bool...>
struct bar;

// true case
template <std::size_t ... Is, std::size_t I, bool ... Bs>
struct bar<std::index_sequence<Is...>, I, true, Bs...>
   : public bar<std::index_sequence<Is..., I>, I+1U, Bs...>
 { };

// false case
template <std::size_t ... Is, std::size_t I, bool ... Bs>
struct bar<std::index_sequence<Is...>, I, false, Bs...>
   : public bar<std::index_sequence<Is...>, I+1U, Bs...>
 { };

// end case
template <typename T, std::size_t I>
struct bar<T, I>
 { using type = T; };

template <bool ... Bs>
struct foo : public bar<std::index_sequence<>, 0U, Bs...>
 { };

int main()
 {
   static_assert( std::is_same<typename foo<false, true, true>::type,
                               std::index_sequence<1U, 2U>>{}, "!" );
 }

If you don't like the recursive solutions, I propose (just for fun) another solution based of std::tuple_cat

#include <tuple>
#include <utility>
#include <type_traits>

template <std::size_t, bool>
struct baz
 { using type = std::tuple<>; };

template <std::size_t I>
struct baz<I, true>
 { using type = std::tuple<std::integral_constant<std::size_t, I>>; };

template <std::size_t I, bool B>
using baz_t = typename baz<I, B>::type;

template <typename, bool...>
struct bar;

template <std::size_t ... Is, bool ... Bs>
struct bar<std::index_sequence<Is...>, Bs...>
 {
   template <std::size_t ... Js>
   constexpr static std::index_sequence<Js...>
      func (std::tuple<std::integral_constant<std::size_t, Js>...> const &);

   using type = decltype(func(std::tuple_cat(baz_t<Is, Bs>{}...)));
 };


template <bool ... Bs>
struct foo : public bar<std::make_index_sequence<sizeof...(Bs)>, Bs...>
 { };

int main()
 {
   static_assert( std::is_same<typename foo<false, true, true>::type,
                               std::index_sequence<1U, 2U>>{}, "!" );
 }
Dercy answered 4/3, 2018 at 16:23 Comment(2)
+1 : I prefer your first version ^^. However I made my own constepr_array and it solves the problem :). ThanksInstrument
@AntoineMorrier - The second one is mainly for fun. Anyway I think is a interesting technique because, using std::tuple_cat permit to insert, on not insert, something at compile time and avoiding a recursive solution.Dercy

© 2022 - 2024 — McMap. All rights reserved.