最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c++ - Define static constexpr native array member outside of class - Stack Overflow

programmeradmin2浏览0评论

The following code works just fine with s_digitU2s defined in class:

#include <iostream>

class Foo {
public:
    template <class Index>
    static constexpr const char* digitU2(const Index a) {
        return s_digitU2s[a];
    }

private:
    static constexpr const char* s_digitU2s[100] = {
        "00", "01", "02", "03", "04", "05", "06", "07", "08", "09",
        "10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
        "20", "21", "22", "23", "24", "25", "26", "27", "28", "29",
        "30", "31", "32", "33", "34", "35", "36", "37", "38", "39",
        "40", "41", "42", "43", "44", "45", "46", "47", "48", "49",
        "50", "51", "52", "53", "54", "55", "56", "57", "58", "59",
        "60", "61", "62", "63", "64", "65", "66", "67", "68", "69",
        "70", "71", "72", "73", "74", "75", "76", "77", "78", "79",
        "80", "81", "82", "83", "84", "85", "86", "87", "88", "89",
        "90", "91", "92", "93", "94", "95", "96", "97", "98", "99"
    };
};

int main() {
    std::cout << Foo::digitU2(42) << std::endl;
    return 0;
}

However I would like to define s_digitU2s outside of Foo essentially to tuck it out of sight (in a "header implementation file" my header file will include after Foo is defined). I will also be making 3 and 4 digit members (with 1,000 and 10,000 elements respectively) and they would even further clutter Foo

I am using C++17 and not finding the syntax needed to define a static constexpr native array member outside a class

The following code works just fine with s_digitU2s defined in class:

#include <iostream>

class Foo {
public:
    template <class Index>
    static constexpr const char* digitU2(const Index a) {
        return s_digitU2s[a];
    }

private:
    static constexpr const char* s_digitU2s[100] = {
        "00", "01", "02", "03", "04", "05", "06", "07", "08", "09",
        "10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
        "20", "21", "22", "23", "24", "25", "26", "27", "28", "29",
        "30", "31", "32", "33", "34", "35", "36", "37", "38", "39",
        "40", "41", "42", "43", "44", "45", "46", "47", "48", "49",
        "50", "51", "52", "53", "54", "55", "56", "57", "58", "59",
        "60", "61", "62", "63", "64", "65", "66", "67", "68", "69",
        "70", "71", "72", "73", "74", "75", "76", "77", "78", "79",
        "80", "81", "82", "83", "84", "85", "86", "87", "88", "89",
        "90", "91", "92", "93", "94", "95", "96", "97", "98", "99"
    };
};

int main() {
    std::cout << Foo::digitU2(42) << std::endl;
    return 0;
}

However I would like to define s_digitU2s outside of Foo essentially to tuck it out of sight (in a "header implementation file" my header file will include after Foo is defined). I will also be making 3 and 4 digit members (with 1,000 and 10,000 elements respectively) and they would even further clutter Foo

I am using C++17 and not finding the syntax needed to define a static constexpr native array member outside a class

Share Improve this question edited Mar 17 at 17:16 asimes asked Mar 17 at 16:18 asimesasimes 6,1465 gold badges45 silver badges80 bronze badges 24
  • 1 If I understood correctly, there should be no problem moving s_digitU2s out of the class. See demo. – wohlstad Commented Mar 17 at 16:21
  • @wohlstad Can it be a member of Foo? My other definitions of Foo are after Foo has been declared – asimes Commented Mar 17 at 16:29
  • 1 If I get it right it's only the declaration that should be moved out of the class: godbolt./z/saz1YK9W6 – Oersted Commented Mar 17 at 16:33
  • 1 Do you want it constexpr or not? If you want it constexpr, you can't "tuck it out of the way". Because the compiler needs to see it at compile time. – Eljay Commented Mar 17 at 16:49
  • 1 @asimes Ah, C++17... ok – Ted Lyngmo Commented Mar 17 at 17:16
 |  Show 19 more comments

4 Answers 4

Reset to default 6

Constexpr variables must be initialized where they are declared.

Given that, you may use this C++17 approach, which is largely what @Oersted suggested

#include <array>
#include <iostream>
#include <string_view>

static constexpr auto initArray()
{
    return std::array{
        "00", "01", "02", "03", "04", "05", "06", "07", "08", "09",
        "10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
        "20", "21", "22", "23", "24", "25", "26", "27", "28", "29",
        "30", "31", "32", "33", "34", "35", "36", "37", "38", "39",
        "40", "41", "42", "43", "44", "45", "46", "47", "48", "49",
        "50", "51", "52", "53", "54", "55", "56", "57", "58", "59",
        "60", "61", "62", "63", "64", "65", "66", "67", "68", "69",
        "70", "71", "72", "73", "74", "75", "76", "77", "78", "79",
        "80", "81", "82", "83", "84", "85", "86", "87", "88", "89",
        "90", "91", "92", "93", "94", "95", "96", "97", "98", "99"
    };
}

class Foo {
public:
    template <class Index>
    static constexpr const char * digitU2(const Index a) {
        return s_digitU2s[a];
    }

private:
    static constexpr  std::array s_digitU2s = initArray();
};

int main() {
    std::cout << Foo::digitU2(42u) << std::endl;
    return 0;
}

There's no good solution for the problem, as the standard states (from c++ 11) that a static constexpr member should always be defined inside the class:

If a static data member of LiteralType is declared constexpr, it must be initialized with an initializer in which every expression is a constant expression, right inside the class definition.

https://en.cppreference/w/cpp/language/static

If you still want to absolutely have it outside of the class declaration you could define a global constexpr variable and then assign the static member to the global variable, although I would recommend a macro approach if you want to keep the data only inside the class:

Class.h

#include <iostream>
#include "Definition.h"

class Foo {
public:
    template <class Index>
    static constexpr const char* digitU2(const Index a) {
        return s_digitU2s[a];
    }

private:
    static constexpr const char* s_digitU2s[100] = S_DIGITU2S_IMPL;
};

Definition.h

#define S_DIGITU2S_IMPL {\
        "00", "01", "02", "03", "04", "05", "06", "07", "08", "09",\
        "10", "11", "12", "13", "14", "15", "16", "17", "18", "19",\
        "20", "21", "22", "23", "24", "25", "26", "27", "28", "29",\
        "30", "31", "32", "33", "34", "35", "36", "37", "38", "39",\
        "40", "41", "42", "43", "44", "45", "46", "47", "48", "49",\
        "50", "51", "52", "53", "54", "55", "56", "57", "58", "59",\
        "60", "61", "62", "63", "64", "65", "66", "67", "68", "69",\
        "70", "71", "72", "73", "74", "75", "76", "77", "78", "79",\
        "80", "81", "82", "83", "84", "85", "86", "87", "88", "89",\
        "90", "91", "92", "93", "94", "95", "96", "97", "98", "99"\
    }

You can declare a static data member without constexpr, then define it with constexpr outside the class:

class Foo {
    // ...
private:
    static const char* const s_digitU2s[100];
};

inline constexpr const char* Foo::s_digitU2s[100] = { /* ... */ };

Note that the in-class declaration must have the correct type. Because constexpr attaches to the variable as a whole and makes it const, the type of s_digitU2s in its definition is "array of 100 const const char*s", i.e., "array of 100 const char* consts".

(You can define the function digitU2 in the class definition, just like you're doing now. But if for some reason you need to separate the declaration and definition, be aware that the rules for functions are different from the rules for variables. For a constexpr function, all declarations of the function must be constexpr, but you're allowed to have forward declarations.)

Ok, the following doesn't answer the question but provides a generic way to build your string constants array instead of writing every values.

The idea is based on this post. One uses NumberAsArray for building a const char array from an integer provided as a non-type template, then an array of std::string_view instances is built through a generateTextHelper function. Finally, one can call generateTextArray for getting the required constexpr array.

#include <string_view>
#include <array>

////////////////////////////////////////////////////////////////////////////////
template <std::size_t I>
struct NumberAsArray 
{
    static constexpr const char value[] {  (I/10 + '0'), (I%10 + '0'), 0 };
};

template <unsigned int... Is>
constexpr auto generateTextHelper(std::integer_sequence<unsigned int, Is...>) 
{
    return std::array<std::string_view, sizeof...(Is)> { { NumberAsArray<Is>::value... } };
}

constexpr auto generateTextArray() 
{
    return generateTextHelper(std::make_integer_sequence<unsigned int, 100u>());
}

////////////////////////////////////////////////////////////////////////////////
class Foo {
public:
    template <class Index>
    static constexpr auto digitU2(const Index a) {
        return generateTextArray()[a];
    }
};

////////////////////////////////////////////////////////////////////////////////
static_assert (Foo::digitU2(42) == "42");

int main()  {}

Demo

You can also do all the stuff with a little c++17 compatible helper ipack that "expands" a given integer into a parameters list that can be used in a fold expression. Since it makes it possible to define s_digitU2s quite simply, it becomes reasonable to put it back in the Foo class;

#include <string_view>
#include <array>
#include <tuple>
#include <iostream>

////////////////////////////////////////////////////////////////////////////////
template<typename integer_type, integer_type...Is>
constexpr auto gen_int_tuple (std::integer_sequence<integer_type,Is...>) 
{ 
    return std::tuple <std::integral_constant<integer_type,Is>...>{}; 
}

template<std::size_t N, typename FCT, typename integer_type=char>
constexpr decltype(auto) ipack (FCT fct)
{
    return std::apply (
        [fct](auto...arg) -> decltype(auto) { return fct(arg...); }, 
        gen_int_tuple<integer_type> (std::make_integer_sequence<integer_type,N>{})
    );
}

////////////////////////////////////////////////////////////////////////////////
class Foo {
public:
    template <class Index>
    static constexpr auto digitU2(const Index a) {
        return s_digitU2s[a];
    }

    static constexpr std::array s_digitU2s = ipack<100> ([](auto...Is)  
    {
        return std::array<const char[3], sizeof...(Is)> {{  {(Is/10+'0'), (Is%10+'0'), 0}... }};
    });
};

////////////////////////////////////////////////////////////////////////////////
int main()  
{
    std::cout << Foo::digitU2(42u) << "\n";
    return 0;
}

Demo

Note however that such a solution works only for not too big N values provided to ipack since its implementation relies on building a tuple of N entries.

发布评论

评论列表(0)

  1. 暂无评论