40

Consider:

static constexpr unsigned num_points{ 7810 };
std::array< double, num_points > axis;

for (int i = 0; i < num_points; ++i)
{
    axis[i] = 180 + 0.1 * i;
}

axis is a class-wide constant. I want to avoid initializing it like any other global variable. Can it be done at compile time?


This is the final class in its entirety:

// https://www.nist.gov/pml/atomic-spectroscopy-compendium-basic-ideas-notation-data-and-formulas/atomic-spectroscopy
// https://www.nist.gov/pml/atomic-spectra-database
struct Spectrum
{
    static constexpr unsigned _num_points{ 7810 };
    using Axis = std::array< double, _num_points >;

    static constexpr Axis _x{ [] ()            // wavelength, nm
        {
            Axis a {};
            for( unsigned i = 0; i < _num_points; ++i )
            {
                a[ i ] = 180 + 0.1 * i;
            }
            return a;
        } () };
    Axis _y {};                                // radiance, W·sr−1·m−2
};

The mixing of code and variables is unsightly, but at least the formula is right in front of the reader's eyes. Any other solution involved a lot of typing in order to get the in-class defined constant and type.

Or if I change my heart, I can simply return the lambda at runtime.

Vorac
  • 8,726
  • 11
  • 58
  • 101
  • 2
    Yes, see https://stackoverflow.com/a/56376301/2466431 – JVApen May 30 '19 at 18:34
  • 1
    If your data is really read-only with this pattern, for *most* use cases on most hardware you're better off computing it at runtime. 7810 * 8 bytes is a big cache footprint for the array. Loading a base + scale factor is only 2 doubles = 16 bytes. Computing at runtime is cheap: one int->FP conversion, and one FMA or a mul + add. (Plus loading the constants). So yes on a cache hit the LUT is faster, but especially for repeated use inside a loop just computing is often going to be good. – Peter Cordes Jun 01 '19 at 11:22

3 Answers3

41

For completeness' sake, here's a version that does not require the definition of a function but instead uses a lambda. C++17 introduced the ability of using lambdas in constant expressions, so you can declare your array constexpr and use a lambda to initialize it:

static constexpr auto axis = [] {
    std::array<double, num_points> a{};
    for (int i = 0; i < num_points; ++i) {
        a[i] = 180 + 0.1 * i;
    }
    return a;
}();

(Note the () in the last line, which calls the lambda right away.)

If you don't like the auto in the axis declaration because it makes it harder to read the actual type, but you don't want to repeat the type inside the lambda, you can instead do:

static constexpr std::array<double, num_points> axis = [] {
    auto a = decltype(axis){};
    for (int i = 0; i < num_points; ++i) {
        a[i] = 180 + 0.1 * i;
    }
    return a;
}();
Nikos C.
  • 50,738
  • 9
  • 71
  • 96
32

Here is the full compileable code:

#include <array>

template<int num_points>
static constexpr std::array<double, num_points> init_axis() {
    std::array<double, num_points> a{};
    for(int i = 0; i < num_points; ++i) 
    {
        a[i] = 180 + 0.1 * i;
    }
    return a;
};

struct Z {
    static constexpr int num_points = 10;
    static constexpr auto axis = init_axis<num_points>();
};
SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • 3
    Suggest substituting `std::array` for `auto` so the reader doesn't have to check the initializing function to know the type. – doug May 30 '19 at 18:59
  • 4
    @doug I find it to be the matter of preference. Some people like to know the type, some people prefer less `type`ing (pun natural!). – SergeyA May 30 '19 at 19:02
  • I prefer auto too, for the same reason. But when the type isn't obvious for someone reading the line of code, including me a month later, I'll put in the extra typing. Probably helpful for readers coming to the question as well. – doug May 30 '19 at 19:06
  • 1
    @doug your code editor doesn't give you the type and doesn't allow you to jump to function in a single click? – SergeyA May 30 '19 at 19:08
  • Of course. But when just scanning a screen of code it doesn't. Have to put the cursor on the variable. I prefer to avoid that. – doug May 30 '19 at 19:09
16

There's also the std::index_sequence trick (Wandbox example):

template <unsigned... i>
static constexpr auto init_axis(std::integer_sequence<unsigned, i...>) {
   return std::array{(180 + 0.1 * i)...};
};

static constexpr auto axis = init_axis(std::make_integer_sequence<unsigned, num_points>{});
metalfox
  • 6,301
  • 1
  • 21
  • 43