在下面的代码中,can_foo
概念测试是否 foo()
可以在类型的实例上调用成员函数。我将使用它来测试两个模板的实例:base
有条件地启用 foo
成员函数,和 derived
覆盖foo
调用其基础的实现:
template <typename T>
concept can_foo = requires(T v) {
v.foo();
};
template <bool enable_foo>
struct base {
void foo()
requires enable_foo
{}
};
template <typename T>
struct derived : T {
void foo()
{
static_cast<T&>(*this).foo();
}
};
如果我测试 base
的实例是否存在模板满足了这个概念,它达到了我的预期:
static_assert(can_foo<base<true>>); //okay
static_assert(not can_foo<base<false>>); //okay
当我将这些类型包装在 derived
中时,我明白了:
static_assert(can_foo<derived<base<true>>>); //okay
static_assert(not can_foo<derived<base<false>>>); //error: static assertion failed
这太令人惊讶了!我预计derived<base<false>>
不会满足can_foo
- 其foo
的定义使用给定 T = base<false>
无效的表达式,并且在评估的上下文中使用该概念测试的相同表达式会导致错误:
int main()
{
derived<base<false>> v{};
v.foo(); //error
}
错误消息不在调用站点,这可能是相关的;它引用了 derived<>::foo
的正文。来自叮当:
<source>:18:32: error: invalid reference to function 'foo': constraints not satisfied
static_cast<T&>(*this).foo();
^
<source>:31:7: note: in instantiation of member function 'derived<base<false>>::foo' requested here
v.foo(); //"invalid reference to function 'foo'"
^
<source>:10:18: note: because 'false' evaluated to false
requires enable_foo
^
铿锵:https://godbolt.org/z/vh58TTPxo 海湾合作委员会:https://godbolt.org/z/qMPrzznar
两个编译器都会产生相同的结果,因此我认为问题在于我忽略了标准中的微妙之处。添加can_foo<T>
限制为 derived<T>
或derived<T>::foo
“修复”这个问题(即 derived<base<false>>
将不再满足 can_foo
),并且在代码审查中我认为应该存在这个约束 - 但这仍然是令人惊讶的行为,我想了解发生了什么上。
那么:为什么 derived<false>
会这样?满足can_foo
?
最佳答案
requires-expression 只能检测所测试表达式的“直接上下文”中的无效构造。特别是
requires(T v) {
v.foo();
};
不会检查调用 v.foo()
是否确实格式良好。如果v.foo()
由于 foo
体内的结构不正确,因此格式不正确。函数,这不会被 requires-expression 检测到,因为主体不在直接上下文中。
问题是,接下来会发生什么? requires-expression 是否应该去实例化 foo
的主体并给出一个硬错误,或者它应该返回 true 并稍后当您尝试调用 v.foo()
时给您一个硬错误?答案是第二个:不执行实例化,因为不需要。请参阅[temp.inst]/5
Unless a function template specialization is a declared specialization, the function template specialization is implicitly instantiated when the specialization is referenced in a context that requires a function definition to exist or if the existence of the definition affects the semantics of the program. [...]
[temp.inst]/11 另外意味着不允许实现实例化定义除非需要它。
在未评估的上下文中,调用 v.foo()
不需要 foo
的定义存在,因为 it is not odr-used unless it is potentially evaluated和 it is normally the ODR that requires a definition to exist 。 (但是,在两种情况下,即使在未求值的上下文中,引用函数也要求其定义存在:当函数具有推导的返回类型或需要常量求值时 ( [temp.inst]/8 ))。由于该定义不需要存在,因此不会实例化该定义。
您可能想要修改 derived::foo
以便它从 T::foo
传播约束,因此可以通过 can_foo<derived<T>>
检测到格式错误。 :
void foo() requires can_foo<T> {
static_cast<T&>(*this).foo();
}
关于c++ - 概念是由看似会产生无效表达式的类型满足的,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75278546/