2

I'm generating an constexpr std::array inside of a constexpr class, but I only want one instance of that class over all of the project where it is used.

I had originally made it a global, but then I found out that these globals are duplicated if I iterate over the array in multiple translation units. I then attempted to use a constexpr function, but I can't have a static inside of such a function. I'm using c++14, so I can't use an inline constexpr variable. An extern constexpr variable doesn't make sense because if you separate the declaration from the definition, then how can the constexpr values be dealt with in a compile time rather than a runtime manner when only one item is needed?

Are there any other options?

EDIT: See https://godbolt.org/z/5PcboYov4

Please remember that I'm not defining a regular variable. I'm defining a constexpr variable. The difference is IMPORTANT. It is being used in both non-constexpr and constexpr contexts.

Bob__
  • 12,361
  • 3
  • 28
  • 42
Adrian
  • 10,246
  • 4
  • 44
  • 110
  • 2
    _"I had originally made it a global, but then I found out that these globals are duplicated if I iterate over the array in multiple transition units. "_ - I don't think you did it correctly then. – Ted Lyngmo Jan 04 '23 at 21:26
  • Presumably when you tried to make it global you defined it in a header file. That's not the correct way to do it. *Declare* it in a header file, *define* it in a cpp file. – john Jan 04 '23 at 21:30
  • Can't declare a `constexpr` variable. It must be defined. – Adrian Jan 04 '23 at 21:31
  • "but I only want one instance of that class over all of the project where it is used" - You can write the class in the [singleton design pattern style](https://stackoverflow.com/questions/1008019/c-singleton-design-pattern) – CleanCoder265 Jan 04 '23 at 21:33
  • @CleanCoder265, how would I structure the code such that a singleton would work for a `constexpr` variable that wouldn't conflict with all the reasons I stated above? – Adrian Jan 04 '23 at 21:44
  • @TedLyngmo, then by all means, please enlighten me as to how to do it. – Adrian Jan 04 '23 at 21:49
  • Are you stuck with C++14? C++17 allows you to declare inline variables. – NathanOliver Jan 04 '23 at 21:52
  • @NathanOliver, yes. I cannot move to a newer compiler. – Adrian Jan 04 '23 at 22:06
  • @Adrian Well, you could put it in a singleton with a `constexpr` constructor and create a `static constexpr` instance. Something [like this](https://godbolt.org/z/399W6KoK6) (see the `get_instance()` function in `constexpr_impl.cpp`) – Ted Lyngmo Jan 04 '23 at 22:29
  • @TedLyngmo, and did you look at the disassembly? What is a `constexpr` function/variable supposed to do? – Adrian Jan 04 '23 at 22:53
  • @Adrian No I didn't look at the assembly. I don't understand the second question. – Ted Lyngmo Jan 04 '23 at 23:02
  • @TedLyngmo, sorry, I meant do you know what the purpose of a `constexpr` is for? It's so that accessing the data in another region of memory isn't necessary or that the algorithm used to grab the data is run at compile time rather than runtime. If you look at the disassembly of your suggestion, you'll find that `fn1`/`fn2` reference the data stored, yet in mine, it will just show up in the disassembly directly. If the `constexpr` is not directly available to the transition unit, the compiler will not be able to generate that optimization. – Adrian Jan 09 '23 at 14:44
  • @TedLyngmo, even `fn3`/`fn4` has an optimization where the 1st and last element are not accessed through the array. They too are inlined into the function. – Adrian Jan 09 '23 at 14:51
  • @Adrian _"do you know what the purpose of a `constexpr` is for"_ - Yes. What you describe sounds more like `consteval` than `constexpr`, but I apparently misunderstood the issue you tried to solve. – Ted Lyngmo Jan 09 '23 at 15:18
  • @TedLyngmo, yes and no. `consteval` limits a function/template function to be only evaluated at compile time. A `constexpr` function/template function allows it to be evaluated at compile time and runtime. Also, `consteval` isn't used for values and is limted to c++20. – Adrian Jan 09 '23 at 15:38
  • @Adrian Yes, and what you described sounded like you wanted to restrict it to compile time only which is why I said it sounded more like `consteval`. Oh well ... I'm glad there was a solution to the problem anyway. – Ted Lyngmo Jan 09 '23 at 15:40

1 Answers1

5

Variable templates have external linkage, even if they are const-qualified:

template<typename = void>
constexpr std::array my_array{/*...*/};

// use my_array<> to access the array.

This is however only since DR 2387, which I guess should also apply to C++14 although it was resolved only in 2019.

If the compiler does not implement it, then you can add extern explicitly. As far as I can tell that is allowed and the declaration will still be a definition because it has an initializer. Because it is a template, there is no ODR violation for multiple definitions:

template<typename = void>
extern constexpr std::array my_array{/*...*/};

// use my_array<> to access the array.

For some reason I forgot the most obvious workaround using a static data member:

struct my_array_wrapper {
    static constexpr std::array</*...*/> my_array{/*...*/};
};

// in one translation unit
constexpr std::array</*...*/> my_array_wrapper::my_array;

Then my_array_wrapper::my_array can be used. The definition in the translation unit is required if the array is ODR-used before C++17 only. Static data members in a class with external linkage also have external linkage, regardless of whether they are const-qualified.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • Oh, that was a good thought, but alas, it doesn't work. See https://godbolt.org/z/xb7P91WW6 – Adrian Jan 04 '23 at 21:36
  • @Adrian You made it explicitly have internal linkage by adding `static`. Remove it. – user17732522 Jan 04 '23 at 21:40
  • Still didn't work. https://godbolt.org/z/qcK7E3zeM – Adrian Jan 04 '23 at 21:42
  • @Adrian Seems that GCC didn't implement [DR 2387](https://cplusplus.github.io/CWG/issues/2387.html) then, although it seems to me that it should apply to C++14 as well. Give me a moment, there should be a workaround... – user17732522 Jan 04 '23 at 21:47
  • @Adrian Both GCC and Clang accept simply adding `extern` to the variable template. I am not sure whether this is correct. I'll need to check again... – user17732522 Jan 04 '23 at 21:49
  • Thanks. Worked great, I think. I'll have to try it on all the compilers we are using, but I think that should work as expected. – Adrian Jan 04 '23 at 22:55
  • A workaround before C++17 is to have the variable be a static member. This was using the exact same linker mechanisms as inline variables, but was arcane hence their introduction. – Passer By Jan 05 '23 at 07:57
  • @PasserBy True, I don't know why I didn't consider that. I have added it now. – user17732522 Jan 05 '23 at 10:25
  • Hmmm, I can't seem to get the `static class` member to work properly. See `constexpr_header.h` in https://godbolt.org/z/rKfzcj6oo. – Adrian Jan 09 '23 at 15:33
  • FYI: In your example, the member isn't declared as a `static` member. – Adrian Jan 09 '23 at 16:02
  • @Adrian Fixed the missing `static`. As I mentioned, before C++17 you need to have a definition (without initializer) for the member in one translation unit if you odr-use it. Forming a reference to the member (as in the `constexpr_var`) is an odr-use. Starting with C++17 `constexpr` static data members are implicitly `inline`, so that the declaration in the class is also the definition. – user17732522 Jan 09 '23 at 18:25
  • Oh, that's weird, but it works. I like the template version better as I don't have to worry about a definition in another source file. Thx for the info. – Adrian Jan 10 '23 at 03:15