Сделать С++ 14 constexpr функцией С++ 11 compatible

Я написал класс 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 элементу constexpr но только в С++ 14. Проблема заключается в функции linearized_index. Он вычисляет линеаризованный индекс во время компиляции. Для этого он определенным образом сводит кортеж индексов и кортеж измерения. Для этого сокращения мне нужна локальная переменная внутри функции, но это не разрешено в С++ 11. Моя среда не позволяет использовать С++ 14. Могу ли я каким-то образом переписать эту функцию для работы с С++ 11?

Я подготовил полный (не очень минимальный) пример, который компилируется в С++ 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 на Wandbox (С++ 14) и Live на Wandbox (С++ 11)

Ответы

Ответ 1

Важнейшей частью использования index является итеративный цикл:

index = (index*a) + b

В вашем собственном решении С++ 14 используется трюк для распаковки пакета параметров. В С++ 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 потому что конструктор std::pair в С++ 11 не 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];
    }

Демо-версия

Ответ 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_sequence, meta::make_index_sequence или meta::pack_element.

Ниже приведен полный пример компиляции С++ 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 предложение: если вы добавляете следующие функции 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...); }

Ответ 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 ...);
}

онлайн-компилятор | godbolt

Ответ 4

Мне удалось получить совместимое с С++ 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