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
4 Answers
Reset to default 6Constexpr 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* const
s".
(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.
s_digitU2s
out of the class. See demo. – wohlstad Commented Mar 17 at 16:21Foo
? My other definitions ofFoo
are afterFoo
has been declared – asimes Commented Mar 17 at 16:29constexpr
or not? If you want itconstexpr
, 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