【问题标题】:Make C++14 constexpr function C++11 compatible使 C++14 constexpr 函数与 C++11 兼容
【发布时间】:2018-10-29 14:36:54
【问题描述】:

我编写了一个类multi_array,它是std::array 对多个维度的扩展。

template <typename T, std::size_t... N>
class multi_array {
    template <std::size_t... I, typename... Idx>
    constexpr std::size_t linearized_index(meta::index_sequence<I...>,
                                           Idx... idx) const {
        std::size_t index = 0;
        using unpack = std::size_t[];
        (void)unpack{0UL,
                     ((void)(index = (index + unpack{std::size_t(idx)...}[I]) *
                                     meta::pack_element<I + 1, N...>::value),
                      0UL)...};
        return index + unpack{std::size_t(idx)...}[sizeof...(idx) - 1];
    }

    // Storage
    T m_data[meta::product<N...>::value];

    //...
};

我已经设法获得 constexpr 元素访问权限,但仅限于 C++14。问题是函数linearized_index。它在编译时计算线性化索引。为了做到这一点,它以某种方式减少了索引的元组和维度的元组。对于这种减少,我需要函数内部的局部变量,但这在 C++11 中是不允许的。我的环境不允许使用 C++14。我可以以某种方式重写此函数以使用 C++11 吗?

我准备了一个用 C++14 编译的完整(不是那么小)示例。

#include <cstddef> // std::size_t

namespace meta {

// product

template <std::size_t...>
struct product;

template <std::size_t head, std::size_t... dim>
struct product<head, dim...> {
    static constexpr std::size_t const value = head * product<dim...>::value;
};

template <>
struct product<> {
    static constexpr std::size_t const value = 1;
};

// pack_element

template <std::size_t index, std::size_t head, std::size_t... pack>
struct pack_element {
    static_assert(index < sizeof...(pack) + 1, "index out of bounds");
    static constexpr std::size_t const value =
        pack_element<index - 1, pack...>::value;
};

template <std::size_t head, std::size_t... pack>
struct pack_element<0, head, pack...> {
    static constexpr std::size_t const value = head;
};

// index_sequence

// https://stackoverflow.com/a/24481400
template <std::size_t... I>
struct index_sequence {};

template <std::size_t N, std::size_t... I>
struct make_index_sequence : public make_index_sequence<N - 1, N - 1, I...> {};

template <std::size_t... I>
struct make_index_sequence<0, I...> : public index_sequence<I...> {};

} // namespace meta

template <typename T, std::size_t... N>
class multi_array {
    template <std::size_t... I, typename... Idx>
    constexpr std::size_t linearized_index(meta::index_sequence<I...>,
                                           Idx... idx) const {
        std::size_t index = 0;
        using unpack = std::size_t[];
        (void)unpack{0UL,
                     ((void)(index = (index + unpack{std::size_t(idx)...}[I]) *
                                     meta::pack_element<I + 1, N...>::value),
                      0UL)...};
        return index + unpack{std::size_t(idx)...}[sizeof...(idx) - 1];
    }

    // Storage
    T m_data[meta::product<N...>::value];

public:
    constexpr multi_array() {}

    template <typename... U>
    constexpr multi_array(U... data) : m_data{T(data)...} {}

    template <typename... Idx>
    constexpr T operator()(Idx... idx) const noexcept {
        std::size_t index = linearized_index(
            meta::make_index_sequence<sizeof...(idx) - 1>{}, idx...);
        return m_data[index];
    }
};

int main() {
    constexpr multi_array<double, 2, 2> const b = {0, 0, 0, 1};
    static_assert(b(1, 1) == 1, "!");
}

Live on Wandbox (C++14)Live on Wandbox (C++11)

【问题讨论】:

    标签: c++ c++11 templates variadic-templates template-meta-programming


    【解决方案1】:

    使用index 的关键部分是迭代循环:

    index = (index*a) + b
    

    在您自己的 C++14 解决方案中,使用了解包参数包的技巧。在 C++11 中,您可以将其表述为递归 constexpr 函数:

    struct mypair {
        size_t a;
        size_t b;
    };
    
    constexpr std::size_t foo(std::size_t init) {
        return init;
    }
    
    template<class... Pair>
    constexpr std::size_t foo(std::size_t init, mypair p0, Pair... ps) {
        return foo((init+p0.a)*p0.b, ps...);
    }
    

    我们使用mypair而不是std::pair,因为C++11中std::pair的构造函数不是constexpr。那么你的迭代循环可以直译为:

        template <std::size_t... I, typename... Idx>
        constexpr std::size_t linearized_index(meta::index_sequence<I...>,
                                               Idx... idx) const {
            using unpack = std::size_t[];
            return foo(0, mypair{unpack{std::size_t(idx)...}[I], meta::pack_element<I+1, N...>::value}...) + unpack{std::size_t(idx)...}[sizeof...(idx) - 1];
        }
    

    Live Demo

    【讨论】:

      【解决方案2】:

      如果在operator(),而不是调用

       std::size_t index = linearized_index(
          meta::make_index_sequence<sizeof...(idx) - 1>{}, idx...);
      

      你打电话

       std::size_t index = linearized_index<N...>(idx...);
      

      或更好(使operator()constexpr

       return m_data[linearized_index<N...>(idx...)];
      

      你可以递归地重写linearized_index()如下

        // ground case
        template <std::size_t>
        constexpr std::size_t linearized_index (std::size_t idx0) const
         { return idx0; }
      
        // recursive case
        template <std::size_t, std::size_t... Is, typename... Idx>
        constexpr std::size_t linearized_index (std::size_t idx0,
                                                Idx ... idxs) const
         { return idx0 * meta::product<Is...>::value
              + linearized_index<Is...>(idxs...); }
      

      如果你愿意,地面案例可以写成如下

        template <typename = void>
        constexpr std::size_t linearized_index () const
         { return 0; }
      

      请注意,您不再需要 meta::index_sequencemeta::make_index_sequencemeta::pack_element

      以下是完整的 C++11 编译示例

      #include <cstddef> // std::size_t
      
      namespace meta
       {
         template <std::size_t...>
          struct product;
      
         template <std::size_t head, std::size_t... dim>
         struct product<head, dim...>
          { static constexpr std::size_t const value
               = head * product<dim...>::value; };
      
         template <>
         struct product<>
          { static constexpr std::size_t const value = 1; };
      
      } // namespace meta
      
      template <typename T, std::size_t... N>
      class multi_array
       {
         private:
            // ground case
            template <std::size_t>
            constexpr std::size_t linearized_index (std::size_t idx0) const
             { return idx0; }
      
            // alternative ground case
            //template <typename = void>
            //constexpr std::size_t linearized_index () const
            // { return 0; }
      
            // recursive case
            template <std::size_t, std::size_t... Is, typename... Idx>
            constexpr std::size_t linearized_index (std::size_t idx0,
                                                    Idx ... idxs) const
             { return idx0 * meta::product<Is...>::value
                  + linearized_index<Is...>(idxs...); }
      
            // Storage
            T m_data[meta::product<N...>::value];
      
         public:
            constexpr multi_array()
             { }
      
            template <typename ... U>
            constexpr multi_array(U ... data) : m_data{T(data)...}
             { }
      
            template <typename... Idx>
            constexpr T operator() (Idx... idx) const noexcept
             { return m_data[linearized_index<N...>(idx...)]; }
       };
      
      int main()
       {
         constexpr multi_array<double, 2, 2> const b = {0, 0, 0, 1};
      
         static_assert( b(1, 1) == 1, "!" );
      
         constexpr multi_array<double, 4, 3, 2> const c
          { 0, 0,   0, 0,   0, 0,
            0, 0,   0, 0,   0, 0,
            0, 0,   2, 0,   0, 0,
            0, 0,   0, 0,   0, 1};
      
         static_assert( c(3, 2, 1) == 1, "!" );
         static_assert( c(2, 1, 0) == 2, "!" );
       }
      

      奖励建议:如果您添加以下 constexpr 函数(static 方法在 multi_array 内?)

        constexpr static std::size_t prod ()
         { return 1U; }
      
        template <typename ... Args>
        constexpr static std::size_t prod (std::size_t v, Args ... vs)
         { return v * prod(vs...); }
      

      你可以删除struct product来电

        // Storage
        T m_data[prod(N...)];
      

        // recursive case
        template <std::size_t, std::size_t... Is, typename... Idx>
        constexpr std::size_t linearized_index (std::size_t idx0,
                                                Idx ... idxs) const
         { return idx0 * prod(Is...) + linearized_index<Is...>(idxs...); }
      

      【讨论】:

      • 这也是一个很好的答案,我很快就会奖励它。
      • 顺便说一句,我不能在 C++11 中创建 operator() constexpr,因为 constexpr 意味着 const(这很烦人)。
      • @HenriMenke - 也许有两个版本,在operator[]/at() 传统中? template &lt;typename... Idx&gt; constexpr T const &amp; operator() (Idx... idx) const { return m_data[linearized_index&lt;N...&gt;(idx...)]; } template &lt;typename... Idx&gt; T &amp; operator() (Idx... idx) { return m_data[linearized_index&lt;N...&gt;(idx...)]; } ?
      【解决方案3】:

      避免数组的直接方法:

      #include <tuple>
      #include <type_traits>
      #include <cstddef>
      
      template
      <
          typename          x_IndexTypesTuple
      ,   ::std::size_t ... x_dimension
      > class
      t_MultiIndexImpl;
      
      template
      <
          typename          x_LeadingIndex
      ,   typename ...      x_Index
      ,   ::std::size_t     x_leadding_dimension
      ,   ::std::size_t ... x_dimension
      > class
      t_MultiIndexImpl
      <
          ::std::tuple<x_LeadingIndex, x_Index ...>, x_leadding_dimension, x_dimension ...
      > final
      {
          static_assert
          (
              ::std::is_same<::std::size_t, x_LeadingIndex>::value
          ,   "index type must be ::std::size_t"
          );
      
          public: static constexpr auto
          Op
          (
              ::std::size_t const  stride_size
          ,   x_LeadingIndex const leading_index
          ,   x_Index const ...    index
          ) -> ::std::size_t
          {
              return stride_size * leading_index
                  + t_MultiIndexImpl<::std::tuple<x_Index ...>, x_dimension ...>::Op
                  (
                      stride_size * x_leadding_dimension, index ...
                  );
          }
      };
      
      template<> class
      t_MultiIndexImpl<::std::tuple<>> final
      {
          public: static constexpr auto
          Op(::std::size_t const /*stride_size*/) -> ::std::size_t
          {
              return ::std::size_t{0};
          }
      };
      
      template<::std::size_t ... x_dimension, typename ... x_Index> inline constexpr auto
      Caclculate_MultiIndex(x_Index const ... index) -> ::std::size_t
      {
          static_assert
          (
              sizeof...(x_dimension) == sizeof...(x_Index)
          ,   "arguments count must match dimensions count"
          );
          return t_MultiIndexImpl<::std::tuple<x_Index ...>, x_dimension ...>::Op(::std::size_t{1}, index ...);
      }
      

      online compiler | godbolt

      【讨论】:

      • 最终我想在 CUDA 代码中使用 multi_array,而不是 std::tuple :( 当然 +1 是您的好解决方案!
      • @HenriMenke 好吧,这里的元组仅用于打包索引类型,因此从技术上讲,它可以用任何带有参数包的随机类模板替换或完全删除。
      • 您是否总是在std 前加上::?你有没有对简单的std::something有任何问题?
      • @YSC 我为全局命名空间中的所有名称添加前缀::。是的,我确实遇到了代码没有定期遵循相同约定的问题。我对std:: 不是::std:: 也有一些问题:案例1 - 库定义了一个嵌套的std 命名空间,其中包含实现未来标准的东西(用于向后移植),案例2 - enum std{ vector, list ...(用于标准容器的序列化)。所以我看不出有任何理由为 std 命名空间做一个例外并且不使用 :: 作为前缀。
      【解决方案4】:

      我已经设法通过重写函数以递归方式评估来获得与 C++11 兼容的解决方案。这不仅有效,而且更易于阅读:

      template <typename... Idx>
      constexpr std::size_t linearized_index(std::size_t n, Idx... idx) const {
          using unpack = std::size_t[];
          return unpack{std::size_t(idx)...}[n] +
                 (n == 0 ? 0
                         : unpack{std::size_t(N)...}[n] *
                               linearized_index(n - 1, idx...));
      }
      

      我找到了另一种使用模板特化的解决方案,它避免了递归函数调用并将其替换为递归实例化。

      namespace meta {
      
      template <size_t n, size_t... N>
      struct linearized_index {
          template <typename... Idx>
          constexpr std::size_t operator()(Idx... idx) const {
              using unpack = std::size_t[];
              return unpack{std::size_t(idx)...}[n] +
                     unpack{std::size_t(N)...}[n] *
                         linearized_index<n - 1, N...>{}(idx...);
          }
      };
      
      template <size_t... N>
      struct linearized_index<0, N...> {
          template <typename... Idx>
          constexpr std::size_t operator()(Idx... idx) const {
              using unpack = std::size_t[];
              return unpack{std::size_t(idx)...}[0];
          }
      };
      
      } // namespace meta
      

      multi_array 呼叫操作员

      template <typename... Idx>
      constexpr T operator()(Idx... idx) const noexcept {
          return m_data[meta::linearized_index<sizeof...(idx) - 1, N...>{}(
              idx...)];
      }
      

      这会产生完美的组装:https://godbolt.org/g/8LPkBZ

      【讨论】:

      猜你喜欢
      • 2014-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-06
      • 1970-01-01
      • 2014-07-30
      • 2015-01-01
      • 2016-10-05
      相关资源
      最近更新 更多