【问题标题】:C++ constexpr : Compute a std array at compile timeC++ constexpr:在编译时计算一个标准数组
【发布时间】:2018-08-12 07:49:44
【问题描述】:

我想将bool 的“数组”转换为整数序列。 所以我需要在编译时计算一个std::array

这是我的代码

#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
}

调用computeIndices 函数的行出现错误。这段代码 c++14 是否正确?可以不做吗? 我正在使用 MSVC,但出现此错误: 表达式未计算为常量

【问题讨论】:

  • Clang 在使用 c++14 时似乎给出了同样的错误,但是,编译是使用 c++17
  • 嗯,可能函数不是 C++14 中的 constexpr,而是 C++17 中的。与 std::count 相同,在 C++20 中将是 constexpr
  • 确实,看我的回答

标签: c++ c++14 variadic-templates template-meta-programming constexpr


【解决方案1】:

此代码在编译为 C++17 时看起来正确且有效。 它使用std::array::begin,仅在 C++17 中使用constexpr

using clang时可以实现更好的编译错误,其中说明:

<source>:23:25: note: non-constexpr function 'begin' cannot be used in a constant expression
    auto it = array.begin();

【讨论】:

    【解决方案2】:

    还有其他办法吗?

    关于正确性回答 JVApen (+1)。

    一种可能的替代方法是完全避免std::array,并使用模板特化以递归方式构造索引序列

    以下是完整的可编译示例

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

    如果您不喜欢递归解决方案,我建议(只是为了好玩)另一个基于 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>>{}, "!" );
     }
    

    【讨论】:

    • +1 : 我更喜欢你的第一个版本^^。但是我制作了自己的 constepr_array 并解决了问题:)。谢谢
    • @AntoineMorrier - 第二个主要是为了好玩。无论如何,我认为这是一种有趣的技术,因为使用std::tuple_cat 允许在编译时插入,而不是插入,并避免递归解决方案。
    猜你喜欢
    • 2014-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-19
    • 2014-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多