from Shower Thoughts to Ruminations

`constexpr` function parameters

NOTE: Welp, this post is about 3 years late. But… it still seems to come
up from time to time and the technique (trick?) doesn’t seem to be widely known,
so I figure I should write it down.

Introduction

Since C++11, functions can be marked constexpr. What does this really mean?
Well, it specifically does not mean that it will be evaluated at
compile-time. It actually means that it is possible for it to be evaluated
at compile-time.

Why is this important? Well, it means that the function needs to be written in
such a way that the parameters can be compile-time or runtime values.

Why does this restriction on non-type template parameters exist in the first
place?

For one, C++ has a fundamental notion of “same type”. Given values x and y,
we have to be able to answer whether foo<x> and foo<y> are the same type.
Currently, the non-type template parameters are restricted such that this
question can be answered using the built-in value equality comparison.
Once user-defined literal types are involved, we have to be able to find a
unique operator== across translation units to perform equality comparisons,
and also mangle the same types to the same name. This potentially introduces
a lot of complexity into the linker.

NOTE: This doesn’t cover the entirety of the issue since “same type”
doesn’t cover function templates. For example, given the function template f
above, f<std::make_tuple(101, 202)> and f<std::make_tuple(23, 24)> both
have the type void (). Thanks tcanens for the reddit comment!

Can we get around this limitation?

Value-Encoded Type

The std::integral_constant class template takes a type
and a value of that type. It essentially encodes the value as part of the type.

This isn’t directly helpful for us though, since the value still needs to meet
the requirements of a non-type template parameter. However, the idea of shoving
a value inside the type remains useful. We simply need a way to encode the value
inside the type without the value being part of the type.

Okay, I admit that may be a bit confusing. Here is the code:

structS{staticconstexprautovalue(){returnstd::make_tuple(101,202);}};

Here, we’ve created a type S that encodes the value
std::make_tuple(101, 202) without the value actually being part of the
type. The tuple(101, 202) is not really part of the type S, but
we can extract the value from S (via S::value()). The compiler therefore
must somehow associate the value with the type, which I consider to be a
valid encoding.

Note that this macro introduces a new type for every value. We’re essentially
“solving” the problem of performing equality comparison of literal types by
punting it entirely and saying “they’re never equal”. For example,

Initially, I was concerned about the potential of introducing too many types.
But this is actually similar to how lambdas work. Specifically, lambdas
that happen to be lexically equivalent don’t have the same type.

Compile-Time String

Compile-time strings in the context of static reflection and metaprogramming are
a point of discussion in the C++ committee. Meanwhile, this macro can be used to
capture and pass around compile-time strings, or even a
std::string_view.