С++ 11: вычисление времени вычисления массива
Предположим, что у меня есть некоторая функция constexpr f:
constexpr int f(int x) { ... }
И у меня есть некоторая константа int N, известная во время компиляции:
Либо
#define N ...;
или
const int N = ...;
по вашему запросу.
Я хочу иметь массив int X:
int X[N] = { f(0), f(1), f(2), ..., f(N-1) }
так что функция вычисляется во время компиляции, а записи в X вычисляются компилятором, а результаты помещаются в статическую область изображения моего приложения точно так же, как если бы я использовал целые литералы в моем списке инициализаторов X.
Есть ли способ написать это? (Например, с шаблонами или макросами и т.д.)
Лучший у меня: (Благодаря Flexo)
#include <iostream>
#include <array>
using namespace std;
constexpr int N = 10;
constexpr int f(int x) { return x*2; }
typedef array<int, N> A;
template<int... i> constexpr A fs() { return A{{ f(i)... }}; }
template<int...> struct S;
template<int... i> struct S<0,i...>
{ static constexpr A gs() { return fs<0,i...>(); } };
template<int i, int... j> struct S<i,j...>
{ static constexpr A gs() { return S<i-1,i,j...>::gs(); } };
constexpr auto X = S<N-1>::gs();
int main()
{
cout << X[3] << endl;
}
Ответы
Ответ 1
Для решения этой проблемы существует чистая версия С++ 11 (без повышения, без макросов). Используя тот же трюк, что и этот ответ, мы можем построить последовательность чисел и распаковать их, чтобы вызвать f
, чтобы построить std::array
:
#include <array>
#include <algorithm>
#include <iterator>
#include <iostream>
template<int ...>
struct seq { };
template<int N, int ...S>
struct gens : gens<N-1, N-1, S...> { };
template<int ...S>
struct gens<0, S...> {
typedef seq<S...> type;
};
constexpr int f(int n) {
return n;
}
template <int N>
class array_thinger {
typedef typename gens<N>::type list;
template <int ...S>
static constexpr std::array<int,N> make_arr(seq<S...>) {
return std::array<int,N>{{f(S)...}};
}
public:
static constexpr std::array<int,N> arr = make_arr(list());
};
template <int N>
constexpr std::array<int,N> array_thinger<N>::arr;
int main() {
std::copy(begin(array_thinger<10>::arr), end(array_thinger<10>::arr),
std::ostream_iterator<int>(std::cout, "\n"));
}
(Протестировано с помощью g++ 4.7)
Вы можете пропустить std::array
полностью с немного большей работой, но я думаю, что в этом случае он чище и проще использовать std::array
.
Вы также можете сделать это рекурсивно:
#include <array>
#include <functional>
#include <algorithm>
#include <iterator>
#include <iostream>
constexpr int f(int n) {
return n;
}
template <int N, int ...Vals>
constexpr
typename std::enable_if<N==sizeof...(Vals),std::array<int, N>>::type
make() {
return std::array<int,N>{{Vals...}};
}
template <int N, int ...Vals>
constexpr
typename std::enable_if<N!=sizeof...(Vals), std::array<int,N>>::type
make() {
return make<N, Vals..., f(sizeof...(Vals))>();
}
int main() {
const auto arr = make<10>();
std::copy(begin(arr), end(arr), std::ostream_iterator<int>(std::cout, "\n"));
}
Что, возможно, проще.
Ответ 2
Boost.Preprocessor может вам помочь. Однако ограничение состоит в том, что вам нужно использовать интегральный литерал, такой как 10
вместо N
(даже если это константа времени компиляции):
#include <iostream>
#include <boost/preprocessor/repetition/enum.hpp>
#define VALUE(z, n, text) f(n)
//ideone doesn't support Boost for C++11, so it is C++03 example,
//so can't use constexpr in the function below
int f(int x) { return x * 10; }
int main() {
int const a[] = { BOOST_PP_ENUM(10, VALUE, ~) }; //N = 10
std::size_t const n = sizeof(a)/sizeof(int);
std::cout << "count = " << n << "\n";
for(std::size_t i = 0 ; i != n ; ++i )
std::cout << a[i] << "\n";
return 0;
}
Выход (ideone):
count = 10
0
10
20
30
40
50
60
70
80
90
Макрос в следующей строке:
int const a[] = { BOOST_PP_ENUM(10, VALUE, ~) };
расширяется до этого:
int const a[] = {f(0), f(1), ... f(9)};
Более подробное объяснение здесь:
Ответ 3
Если вы хотите, чтобы массив находился в статической памяти, вы можете попробовать следующее:
template<class T> struct id { typedef T type; };
template<int...> struct int_pack {};
template<int N, int...Tail> struct make_int_range
: make_int_range<N-1,N-1,Tail...> {};
template<int...Tail> struct make_int_range<0,Tail...>
: id<int_pack<Tail...>> {};
#include <array>
constexpr int f(int n) { return n*(n+1)/2; }
template<class Indices = typename make_int_range<10>::type>
struct my_lookup_table;
template<int...Indices>
struct my_lookup_table<int_pack<Indices...>>
{
static const int size = sizeof...(Indices);
typedef std::array<int,size> array_type;
static const array_type& get()
{
static const array_type arr = {{f(Indices)...}};
return arr;
}
};
#include <iostream>
int main()
{
auto& lut = my_lookup_table<>::get();
for (int i : lut)
std::cout << i << std::endl;
}
Если вы хотите, чтобы локальная копия массива работала, просто удалите амперсанд.
Ответ 4
Я немного расширил ответ от Flexo и Andrew Tomazos, чтобы пользователь мог указать вычислительный диапазон и функцию, которую нужно оценить.
#include <array>
#include <iostream>
#include <iomanip>
template<typename ComputePolicy, int min, int max, int ... expandedIndices>
struct ComputeEngine
{
static const int lengthOfArray = max - min + sizeof... (expandedIndices) + 1;
typedef std::array<typename ComputePolicy::ValueType, lengthOfArray> FactorArray;
static constexpr FactorArray compute( )
{
return ComputeEngine<ComputePolicy, min, max - 1, max, expandedIndices...>::compute( );
}
};
template<typename ComputePolicy, int min, int ... expandedIndices>
struct ComputeEngine<ComputePolicy, min, min, expandedIndices...>
{
static const int lengthOfArray = sizeof... (expandedIndices) + 1;
typedef std::array<typename ComputePolicy::ValueType, lengthOfArray> FactorArray;
static constexpr FactorArray compute( )
{
return FactorArray { { ComputePolicy::compute( min ), ComputePolicy::compute( expandedIndices )... } };
}
};
/// compute 1/j
struct ComputePolicy1
{
typedef double ValueType;
static constexpr ValueType compute( int i )
{
return i > 0 ? 1.0 / i : 0.0;
}
};
/// compute j^2
struct ComputePolicy2
{
typedef int ValueType;
static constexpr ValueType compute( int i )
{
return i * i;
}
};
constexpr auto factors1 = ComputeEngine<ComputePolicy1, 4, 7>::compute( );
constexpr auto factors2 = ComputeEngine<ComputePolicy2, 3, 9>::compute( );
int main( void )
{
using namespace std;
cout << "Values of factors1" << endl;
for ( int i = 0; i < factors1.size( ); ++i )
{
cout << setw( 4 ) << i << setw( 15 ) << factors1[i] << endl;
}
cout << "------------------------------------------" << endl;
cout << "Values of factors2" << endl;
for ( int i = 0; i < factors2.size( ); ++i )
{
cout << setw( 4 ) << i << setw( 15 ) << factors2[i] << endl;
}
return 0;
}
Ответ 5
Здесь есть немало замечательных ответов. Вопрос и теги указывают c++11
, но через несколько лет некоторые (например, я), наткнувшись на этот вопрос, могут быть открыты для использования c++14
. Если это так, можно сделать это очень чисто и кратко с помощью std::integer_sequence
; кроме того, его можно использовать для создания гораздо более длинных массивов, поскольку текущий "Best я Have" ограничен глубиной рекурсии.
constexpr std::size_t f(std::size_t x) { return x*x; } // A constexpr function
constexpr std::size_t N = 5; // Length of array
using TSequence = std::make_index_sequence<N>;
static_assert(std::is_same<TSequence, std::integer_sequence<std::size_t, 0, 1, 2, 3, 4>>::value,
"Make index sequence uses std::size_t and produces a parameter pack from [0,N)");
using TArray = std::array<std::size_t,N>;
// When you call this function with a specific std::integer_sequence,
// the parameter pack i... is used to deduce the the template parameter
// pack. Once this is known, this parameter pack is expanded in
// the body of the function, calling f(i) for each i in [0,N).
template<std::size_t...i>
constexpr TArray
get_array(std::integer_sequence<std::size_t,i...>)
{
return TArray{{ f(i)... }};
}
int main()
{
constexpr auto s = TSequence();
constexpr auto a = get_array(s);
for (const auto &i : a) std::cout << i << " "; // 0 1 4 9 16
return EXIT_SUCCESS;
}
Ответ 6
Вот более краткий ответ, в котором вы явно объявляете элементы в исходной последовательности.
#include <array>
constexpr int f(int i) { return 2 * i; }
template <int... Ts>
struct sequence
{
using result = sequence<f(Ts)...>;
static std::array<int, sizeof...(Ts)> apply() { return {{Ts...}}; }
};
using v1 = sequence<1, 2, 3, 4>;
using v2 = typename v1::result;
int main()
{
auto x = v2::apply();
return 0;
}
Ответ 7
Как насчет этого?
#include <array>
#include <iostream>
constexpr int f(int i) { return 2 * i; }
template <int N, int... Ts>
struct t { using type = typename t<N - 1, Ts..., 101 - N>::type; };
template <int... Ts>
struct t<0u, Ts...>
{
using type = t<0u, Ts...>;
static std::array<int, sizeof...(Ts)> apply() { return {{f(Ts)...}}; }
};
int main()
{
using v = typename t<100>::type;
auto x = v::apply();
}