C++11: Compile Time Calculation of Array

前端 未结 7 2083
长发绾君心
长发绾君心 2020-11-28 05:25

Suppose I have some constexpr function f:

constexpr int f(int x) { ... }

And I have some const int N known at compile time:

Either<

相关标签:
7条回答
  • 2020-11-28 05:54

    I slightly extended the answer from Flexo and Andrew Tomazos so that the user can specify the computational range and the function to be evaluated.

    #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;
    }
    
    0 讨论(0)
  • 2020-11-28 06:01

    Boost.Preprocessor can help you. The restriction, however, is that you have to use integral literal such as 10 instead of N (even be it compile-time constant):

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

    Output (ideone):

    count = 10
    0
    10
    20
    30
    40
    50
    60
    70
    80
    90
    

    The macro in the following line:

    int const a[] = { BOOST_PP_ENUM(10, VALUE, ~) }; 
    

    expands to this:

    int const a[] = {f(0), f(1), ... f(9)}; 
    

    A more detail explanation is here:

    • BOOST_PP_ENUM
    0 讨论(0)
  • 2020-11-28 06:01

    There are quite a few great answers here. The question and tags specify c++11, but as a few years have passed, some (like myself) stumbling upon this question may be open to using c++14. If so, it is possible to do this very cleanly and concisely using std::integer_sequence; moreover, it can be used to instantiate much longer arrays, since the current "Best I Have" is limited by recursion depth.

    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;
    
    }
    
    0 讨论(0)
  • 2020-11-28 06:09

    There is a pure C++11 (no boost, no macros too) solution to this problem. Using the same trick as this answer we can build a sequence of numbers and unpack them to call f to construct a 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"));
    }
    

    (Tested with g++ 4.7)

    You could skip std::array entirely with a bit more work, but I think in this instance it's cleaner and simpler to just use std::array.

    You can also do this recursively:

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

    Which is arguably simpler.

    0 讨论(0)
  • 2020-11-28 06:16

    Here's a more concise answer where you explicitly declare the elements in the original sequence.

    #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;
    }
    
    0 讨论(0)
  • 2020-11-28 06:17

    How about this one?

    #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();
    }
    
    0 讨论(0)
提交回复
热议问题