编辑:重命名,因为我的最终解决方案不使用中毒方法。
我正在寻找一种方法来防止在运行时调用 constexpr 方法。我正在编写一个接受字符串文字的函数,所以我不能简单地使用 NTTP 作为需要constexpr
参数的方式:
template<const char* str>
auto func() {...}
因为这样即使是合法的 constexpr 使用也会变得很麻烦,需要值具有静态链接,并且您不能输入字符串文字。我想要做:
constexpr auto func(const char* str) {...}
原因是因为我根据值列表检查字符串,并希望静态检查参数是否包含在允许的集合中。我可以很容易地做到这一点:只需throw()
'ing 一个constexpr
函数,就可能导致编译时错误。但我不希望生成带有一些导致程序在运行时退出的分支的生产代码的可能性。这将在我的领域造成一个重大问题;此功能在执行“其他重要事情”的程序中是一项不错的功能,如果程序终止,则会发生坏事。
我读到了一大堆可能的方法来做到这一点:
- 使用 C++20
consteval
- 没有 C++20 - 使用 C++20
std::is_constant_evaluated
- 没有 C++20 - 通过将结果返回给未定义的符号(例如
extern int i
,从未定义的符号)在运行时毒化方法。如果在编译时调用该方法,编译器永远不会创建返回该符号的代码,但如果在运行时调用该方法,它会创建返回该符号的代码,从而导致链接错误。工作,但丑陋的链接器错误;不是我最喜欢的。我的帖子中显示了一个版本:Constexpr functions not called at compile-time if result is ignored。 - 在 C++17 中,
noexcept
自动添加到对constexpr
在上下文中实际调用的函数的任何调用constexpr
中。所以你可以做noexcept(foo(1))
vsnoexcept(foo(i))
forconstexpr int foo(int i)
(未明确声明noexcept
) 来检测是否i
导致调用为constexpr
/not。但是您不能在constexpr
接受了某些参数的函数中执行此操作 - 您需要从调用站点执行此操作。所以:可能需要一个辅助宏(不是我最喜欢的,但它有效)。 - 如果 lambda 范围之外的某些变量不是,则创建一个返回类型无效的 lambda
constexpr
。这篇文章详细介绍:https ://stackoverflow.com/a/40413051
所以我倾向于使用#3 或#4 + 宏,但是***这篇文章是关于#5 ***,或者是全新的想法。
例如,任何人都可以想出一种没有 lambda 的方法来执行 #5 吗?在那之后,我想看看我是否可以想出一种在constexpr
函数本身中使用它的方法,而不是要求它从调用站点使用。现在,如果在运行时调用函数,只需尝试毒化constexpr
它,忘记“检测”函数调用是否为constexpr
.
我可以像作者一样通过在其中创建一个 lambda 来重新创建 #5 的结果main
,但这实际上并不是很有用,而且我仍然不相信它是完全合法的。首先,任何可以用 lambda 完成的事情都可以在没有 lambda 的情况下完成——对吧???如果没有 lambda,我什至无法让原作者的方法工作。这似乎是让它在main()
.
以下是我尝试在没有 lambda 的情况下重新创建 #5 的几个想法。具有十亿多个排列的实时示例,但都不起作用: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;
}
还有一件事:我考虑制作一个预定义的允许标签列表(将字符串包装在一个结构中,这样它就可以是一个 NTTP),并将它们放在某种容器中,然后有一个方法来检索他们。问题是:(1)调用站点语法使用它们变得非常冗长,(2)虽然调用站点使用类似的语法很好,但MyTags::TAG_ONE
我的程序需要能够知道完整的集合标签的数量,因此它们需要位于数组或模板变量中,(3)使用数组不起作用,因为获取数组元素会产生一个rvalue
,它没有链接,因此不能作为NTTP,(4)使用具有显式特化的模板变量来定义每个标签需要模板变量是全局范围的,这对我来说效果不佳......
这是我能做的最好的了——我觉得这有点丑……:
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")>();
}