Reputation: 25297
Suppose I have a configuration function to be defined by the user of the library which may or may not be constexpr
.
constexpr int iterations() { return 10; }
// Or maybe:
int iterations() { return std::atoi(std::getenv("ITERATIONS")); }
// I'm okay with requiring the constexpr version to be:
constexpr auto iterations() { return std::integral_constant<int, 10>{}; }
Now I have a function that has different behavior depending on the value of iterations()
, but this function should be constexpr
if iterations
is, because I want the user to be able to use it in constexpr
if they configured the library to allow for it:
/*constexpr*/ std::uint32_t algorithm(std::uint32_t x) {
auto const iterations = ::iterations();
// For illustration purposes
for (int i = 0; i < iterations; ++i) {
x *= x;
}
return x;
}
What can I do to algorithm
to make the function constexpr
if iterations()
is? In short, I want something like constexpr(constexpr(::iterations()))
.
Note that "conditional constexpr
" is usually dependent on a template parameter as in Conditionally constexpr member function in which case the constexpr
keyword can just be used, but in this case, I want the constexpr
to be conditional on something which is not a template parameter, but a statically known function. Marking algorithm
as constexpr
is a compilation error:
error: call to non-'constexpr' function 'bool iterations()'
Upvotes: 3
Views: 435
Reputation: 42554
You can silence the compiler diagnostics by ensuring that there is some set of template parameters and function arguments for which a call to your function is a constant expression even if ::iterations()
is not a constant expression. For example:
template <bool True = true>
constexpr std::uint32_t algorithm(std::uint32_t x) {
if constexpr (True) {
auto const iterations = ::iterations();
for (int i = 0; i < iterations; ++i) {
x *= x;
}
return x;
} else {
return 0;
}
}
algorithm<false>(meow)
is a constant expression whenever meow
is, so a compiler cannot complain (https://godbolt.org/z/GvE9ME).
Upvotes: 4
Reputation: 66200
Taking inspiration from your answer, you can use concepts to impose that iteration()
return a integral constant.
The important part is remember that algorithm()
ha to be a template function or concept (requires
) can't works, so you can impose an unused and defaulted template parameter
//VVVVVVVVVVVVVVVVVVVVVVVVVV <-- add this to activate `requires`
template <typename = void>
constexpr std::uint32_t algorithm(std::uint32_t x)
requires is_integral_constant<decltype(::iterations())>
{ return ::algorithm_impl(x, ::iterations()); }
The following is your example simplified
template <typename T>
constexpr bool is_integral_constant = false;
template <typename T, T value>
constexpr bool is_integral_constant<std::integral_constant<T, value>> = true;
constexpr std::uint32_t algorithm_impl(std::uint32_t x, int iterations) {
for (int i = 0; i < iterations; ++i) {
x *= x;
}
return x;
}
template <typename = void>
constexpr std::uint32_t algorithm(std::uint32_t x)
requires is_integral_constant<decltype(::iterations())>
{ return ::algorithm_impl(x, ::iterations()); }
std::uint32_t algorithm(std::uint32_t x) {
return ::algorithm_impl(x, ::iterations());
}
Upvotes: 2
Reputation: 25297
I'm okay with requiring the constexpr version to be: ...
std::integral_constant<int, 10>
Because you are okay with requiring the constexpr
version of the function to have a different return type, we can detect this special type and condition the constexpr
on that by using constraints (C++20's requires
). Note that we have to further wrap the body in if constexpr
because the compiler still checks the body of the function:
template <typename T>
constexpr bool is_integral_constant = false;
template <typename T, T value>
constexpr bool is_integral_constant<std::integral_constant<T, value>> = true;
constexpr std::uint32_t algorithm_impl(std::uint32_t x, int iterations) {
for (int i = 0; i < iterations; ++i) {
x *= x;
}
return x;
}
constexpr std::uint32_t algorithm(std::uint32_t x)
requires is_integral_constant<decltype(::iterations())> {
if constexpr (is_integral_constant<decltype(::iterations())>) {
return ::algorithm_impl(x, ::iterations());
} else {
// Unreachable, but enough to convince the compiler that there is a
// constexpr friendly path through the function
return 0xDEADBEEF;
}
}
std::uint32_t algorithm(std::uint32_t x) {
return ::algorithm_impl(x, ::iterations());
}
Upvotes: 0