Сделать С++ 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