c++ - 概念是由看似会产生无效表达式的类型满足的

标签 c++ templates language-lawyer c++20 c++-concepts

在下面的代码中,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 evaluatedit 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/

相关文章:

c++ - 我如何使用 C++ 中的类创建和填充链表?

c++ - 优先级队列的自定义比较器

c++ - Template模板参数,为什么要强制类?

c++ - 这个简单的模板类我做错了什么?

c - 整数常量指向的对象的默认类型

c++ - 位域的内存位置

c++ - Borland Builder 2007 - 使 TForm 窗口透明

c++ - 将二维 vector 的值设置为 0 的快速方法

templates - Golang 模板.ParseFiles "not a directory"错误

c++ - 折叠表达式的结合性