EDIT: Renamed, as my final solution does not use a poisoning method.
I'm looking for a way to prevent a constexpr method from being called at runtime. I'm writing a function that accepts a string literal, so I cannot simply use a NTTP as a way to require a constexpr
parameter:
template<const char* str>
auto func() {...}
Because then even legitimate constexpr uses become cumbersome, requiring values to have static linkage, and you can't feed in a string literal. I want to do:
constexpr auto func(const char* str) {...}
The reason is because I check the string against a list of values, and want to STATICALLY check that the parameter is contained in the allowed set. I can do this easily: by just throw()
'ing in a constexpr
function, you can cause a compile-time error. But I do not want the possibility of generating production code with some branch that causes the program to exit at runtime. That would cause a major problem in my field; this feature is a nice-to-have in a program that does "other important stuff" and bad things happen if the program terminates.
I read about a whole bunch of possible ways to do this:
- Use C++20
consteval
- don't have C++20 - Use C++20
std::is_constant_evaluated
- don't have C++20 - Poison the method at runtime by returning a result to an undefined symbol (e.g.
extern int i
that never gets defined). The compiler never creates code which returns that symbol if the method is called at compile-time, but it does if the method is called at runtime, resulting in a link error. Works, but ugly linker error; not my favorite. A version of that is shown in my post here: Constexpr functions not called at compile-time if result is ignored. - In C++17,
noexcept
gets automatically added onto any call to aconstexpr
function that actually gets called in aconstexpr
context. So you can donoexcept(foo(1))
vsnoexcept(foo(i))
forconstexpr int foo(int i)
(not explicitly declarednoexcept
) to detect whetheri
causes the call to beconstexpr
/not. But you can't do that from within aconstexpr
function that has accepted some parameter - you need to do it from the call-site. So: probably requires a helper MACRO (not my favorite but it works). - Create a lambda whose return type is invalid if certain variables outside the scope of the lambda are not
constexpr
. This post goes into detail: https://stackoverflow.com/a/40413051
So I'm leaning towards using #3 or #4 + a macro, but ***this post is about #5 ***, or totally new ideas.
Can anyone come up with a way to do #5 without a lambda, for example? After that, I want to see if I can come up with a way to use it within the constexpr
function itself rather than requiring it to be used from the call-site. For now, just try to poison a constexpr
function if it is called at runtime, forget about "detecting" whether the function call is constexpr
.
I can re-create the results of #5 by creating a lambda in main
as the author did, but that's not actually very useful, and I'm still not convinced that it's fully legal. To start with, anything that can be done with a lambda can be done without a lambda -- right??? I can't even get the original author's method to work without a lambda. That seems like a first required step to get it to work outside of main()
.
Below are a couple ideas I've tried to recreate #5 without a lambda. Live example with a billion more permutations, none of which work: https://onlinegdb.com/B1oRjpTGP
// Common
template<int>
using Void = void;
// Common
struct Delayer {
constexpr auto delayStatic(int input) { return input; }
};
// Attempt 1
template<typename PoisonDelayer>
constexpr auto procurePoison(int i) {
struct Poison {
// error: use of parameter from containing function
// constexpr auto operator()() const -> Void<(PoisonDelayer::delayStatic(i), 0)> {}
} poison;
return poison;
}
// Attempt 2
struct PoisonInnerTemplate {
const int& _i;
// Internal compiler error / use of this in a constexpr
template<typename PoisonDelayer>
auto drink() const -> Void<(PoisonDelayer::delayStatic(_i), 0)> {}
};
int main()
{
auto attempt1 = procurePoison<Delayer>(1);
constexpr int i = 1;
auto attempt2 = PoisonInnerTemplate{i};
attempt2.drink<Delayer>();
return 0;
}
One more thing: I toyed with the idea of making a pre-defined list of allowed tags (wrap the string in a struct so it can be a NTTP), and putting them in some kind of container, and then having a method to retrieve them. The problems are: (1) the call-site syntax gets quite verbose to use them, (2) although it'd be fine for the call-site to use a syntax like MyTags::TAG_ONE
, my program needs to be able to know the full set of tags, so they need to be in an array or a template variable, (3) using an array doesn't work, because getting an array element produces an rvalue
, which doesn't have linkage, so can't be fed as a NTTP, (4) using a template variable with explicit specialization to define each tag requires the template variable to be global-scope, which doesn't work well for me...
This is about the best I can do - I think it's kind of ugly...:
struct Tag {
const char* name;
};
template<auto& tag>
void foo() {}
struct Tags {
static constexpr Tag invalid = {};
static constexpr Tag tags[] = {{"abc"}, {"def"}};
template<size_t N>
static constexpr Tag tag = tags[N];
template<size_t N = 0>
static constexpr auto& getTag(const char* name) {
if constexpr(N<2) {
if(string_view(name)==tag<N>.name) {
return tag<N>;
} else {
return getTag<N+1>(name);
}
} else {
return invalid;
}
}
};
int main()
{
foo<Tags::getTag("abc")>();
}