C++ Logo

std-discussion

Advanced search

Re: constexpr functions and variables

From: Tiago Freire <tmiguelf_at_[hidden]>
Date: Wed, 3 Apr 2024 12:18:06 +0000
For that you would have to understand what each of those things would mean to the compiler in practice.

Most of the compilers work with the principle of a "translation unit", i.e. you compile each of the source files individually, and even tough you can include other files the compiler only sees the current source file that is currently being compiled and what is brough in via the include. It doesn't know whatever else is happening on other source files. Only at the linking stage is all the information collected from all compilation translation units is brough together to string a coherent binary.

When you state:

extern const int myconstant;

what you are telling the compiler is that there's somewhere else in another translation unit, there is a symbol that looks like this:
const int myconstant;

just forget for a moment that we are not able to see it right now.
And this establishes a basic contract of things that you should be able to do with this object:

* You can get the address of this object, it is going to be somewhere in memory, either in a data block or somewhere else.
* You can read it at runtime, but not necessarily at compile time, it is possible that it is only determined at runtime
* At sometime during runtime this object is going to be constructed starting its lifetime, and then destructed, ending its lifetime
* I'm able to interact with at runtime and be able to modify it at runtime in ways that the class defines that I can interact.
* If you don't see it at compile time, it cannot make any assumptions about it, the code has to read from it at runtime.


But
constexpr int myconstant;
means something different:

* you cannot mutate it in any way
* Its construction is defined at compile time, and there's no meaningful "lifetime" to the object
* The object may not necessarily reside anywhere at runtime. Depending on what you do with it, the compiler may decide to create a copy that can be accessed at runtime,
but it doesn't have to. It can decide it is going to embed it into move instructions, simplify it to something else, carve it into pieces, remove it entirely, or it can be many distinct non-unique copies.
* you cannot get a runtime address

These two things are different, and the way you describe it, only the .cpp that has the definition would know that this is an object that may not exist at runtime and may not create one,
while the other ones that only see the "extern" expects that there be some other translation unit that defines this object somewhere at runtime.
And at link time, the linker will never be able to provide an address to "myconstant", because no translation unit defines one.

Of course you can always do this in your cpp file instead:

constexpr int myconstant_constexpr = fun();
const int myconstant = myconstant_constexpr;

Which will be able to fulfill the requirement of having a runtime "myconstant" that can be accessed by other translation units as well as having a constexpr object.
But then the question poses itself, if myconstant is a compile time constant, why not define it constexpr everywhere?

i.e. instead of using
extern const int myconstant;
you would declare
constexpr int myconstant;

and why wouldn't you want to do that?
The reason here could be that then you would need to make the actual value available to the translation unit, and then you would need to expose:
constexpr int myconstant = fun();
and maybe you don't want the function "fun" available to the user because it is doing something funny that you shouldn't use.

It is not a new idea that what you would want to do is to just declare:
constexpr int myconstant;
without defining what it is initialized with in the header file, and only private code would know what it is.

But this poses a problem, what happens if you declare something like:
std::array<char, myconstant> myarray; ???

The translation unit can't see the value of myconstant, so it can not even generate code to deal with this.
It is not until it has seen the .cpp that defines a concrete value for it that it can start to generate code. An although link time code generation is a think, it was never meant to be used to this extent.

You would need to fundamentally change the way code is compiled in order to get this to work.

And that is what cpp modules will fundamentally do. Cpp modules will solve this problem.
The problem is modules still have some fundamental design problems that prevent it from being viable and are troublesome to adopt in its current form.
Allot more work needs to be done, tools that don't exist yet need to be written to make it an easy and viable solution, and that won't be any time soon.



-----Original Message-----
From: Std-Discussion <std-discussion-bounces_at_lists.isocpp.org> On Behalf Of Federico Kircheis via Std-Discussion
Sent: Wednesday, April 3, 2024 11:54
To: cpp-std-discuss <std-discussion_at_[hidden]socpp.org>
Cc: Federico Kircheis <federico_at_[hidden]>
Subject: [std-discussion] constexpr functions and variables

Consider following snippet

----
// .hpp file

extern const int myconstant;

// .cpp file

constexpr int fun(){return 42;}

constexpr int myconstant = fun();
----

It is possible to declare a constant variable in a header file, and then have it constexpr in the translation unit.


This has following advantages:

  * no init order fiasco
  * can be used in constexpr context in one translation unit
  * no recompilation of other translation units if value changes
  * does not require to put the "dependencies" for creating myconstant
in the header file


I found myself more than once wanting to write something like

----
// .hpp file

int function(int);

// .cpp file

constexpr int function(int){return 42;}
----

But it is currently not valid C++ (GCC, for example, fails with "error:
redeclaration 'constexpr int function(int)' differs in 'constexpr' from
previous declaration")

I would like to write something like that because it has the same
advantages of myconstant, except for the init-order-fiasco.


A possible workaround is writing


----
// .hpp file

int function(int);

// .cpp file
namespace{
constexpr int function_imp(int){return 42;}
}

int function(int i){
   return function_imp(i);
}
----

but requires some unnecessary boilerplate code, which cannot even be
automated with a macro, and it gets error prone with functions that
accepts multiple parameters.


Was such difference between constants and functions considered when
constexpr did get introduced?
Or was something similar already proposed?
I could not find any relevant information.

Best

Federico
--
Std-Discussion mailing list
Std-Discussion_at_lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion

Received on 2024-04-03 12:18:17