Reputation: 39881
Here is what I read: using alias for static member functions?
In the answer I see a suggestion to use constexpr
. What is the reason to use constexpr
for void
functions.
Please demonstrate a simple use case. I am new to constexpr
so in complex examples I will not understand the gist.
Upvotes: 8
Views: 1984
Reputation: 785
A reasonable use case is the manipulation of volatile variables. The following is a very simplified case from embedded programming using GNU tools (g++, ld):
To make an address of a peripheral a constexpr
, you need to put it at a fixed location. This must be done in a linker script:
⋮
/* Define output sections */
SECTIONS
{
GPIO 0x48000000 (NOLOAD) : { *(.GPIO) }
⋮
Now, the .GPIO
section is at the fixed address 0x48000400
. Peripherals can be modeled by PODs containing volatile members. In the following example, the POD is named gpio_t
and has just a single member: mode
. The member can be set up in a constexpr
function. Of course, there is no benefit to use a function to set a variable to a constant value. But in real use cases values and addresses have to be calculated. Think, e.g., of setting a divider for a Baud rate.
struct gpio_t {
volatile std::uint32_t mode;
};
__attribute__ ((section (".GPIO"))) gpio_t Gpio = {0};
static constexpr gpio_t *port {&Gpio};
static constexpr void init () {
port->mode = 42u;
};
void main {
init ();
⋮
};
Note: The C-style idiom of casting integers to addresses does not work because it reinterpret_cast<>
does not qualify for creating constexpr
pointers (see Since C++14 illegal). The following fails:
constexpr gpio_t *port {(gpio_t *) 0x48000400};
Upvotes: 2
Reputation: 9416
Rahul's answer cites the standard paragraph which allow void
constexpr
functions, but it doesn't give a use-case. One use-case that comes to my mind would be to have a constexpr
class, and as usual factor out behavior common to method in helper methods. The standard explicitly mentions function doing checks, e.g. assertions. I don't have a concrete example at hand, but I can imagine something like
class A
{
public:
constexpr X doSomething(Y arg1) {
checkInvariant();
constraintOnYArgument(arg1);
// ...
checkInvariant();
}
constexpr X doSomethingElse(Y arg1) {
checkInvariant();
constraintOnYArgument(arg1);
// ...
checkInvariant();
}
private:
constexpr void constraintOnYArguments(Y arg) {
}
constexpr void checkInvariant() {
// some checks
if (!some condition) {
throw std::logic_error("Oh no!");
}
}
};
Upvotes: 6
Reputation: 172578
As per the C++ 14 standard, void is a literal type
A type is a literal type if it is:
— void; or
— a scalar type; or
— a reference type; or
— an array of literal type; or
— a class type (Clause 9) that has all of the following properties: — it has a trivial destructor,
— it is an aggregate type (8.5.1) or has at least one constexpr constructor or constructor template that is not a copy or move constructor, and
— all of its non-static data members and base classes are of non-volatile literal types.
From here:
An arbitrary expression-statement is permitted, in order to allow calls to functions performing checks and to allow assert-like constructs. void also becomes a literal type, so that constexpr functions which exist only to perform such checks may return void.
Upvotes: 2
Reputation: 1
As long as parameter number is an integral constant, this constexpr version will compute the result at compile-time (C++11 compilers only). And when the number is a run-time integer, this same function is perfectly capable of computing the result at run-time. So you don't need two different versions of the same program: one for compile-time and another for run-time. One implementation does it all.
Upvotes: 0