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

3
  • Clang seems to give the same error when using c++14, however, compiles is with c++17 Commented Mar 4, 2018 at 16:03
  • 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++20 Commented Mar 4, 2018 at 16:05
  • Indeed, see my answer Commented Mar 4, 2018 at 16:06

2 Answers 2

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();
Sign up to request clarification or add additional context in comments.

Comments

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>>{}, "!" );
 }

2 Comments

+1 : I prefer your first version ^^. However I made my own constepr_array and it solves the problem :). Thanks
@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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.