需要特定成员返回类型的概念

E_3*_*E_3 12 c++ c++-concepts c++20

我在开始了解 C++20 概念时遇到一些困难。我想定义一个概念,要求类有一个名为的成员,count_该成员必须是类型int

\n
#include <concepts>\n\ntemplate <typename T>\nconcept HasCount = requires(T thing) {\n    { thing.count_ } -> std::same_as<int>;\n};\n
Run Code Online (Sandbox Code Playgroud)\n

以下结构应该满足这个概念:

\n
struct BaseTableChunk {\n    BaseTableChunk* next_;\n    int count_ = 0;\n    int data_[1000];\n};\n
Run Code Online (Sandbox Code Playgroud)\n

然后,以下代码无法编译:

\n
template <HasCount Chunk>\nclass BaseTable {\n    void doSomething();\n};\n\nint main() {\n    BaseTable<BaseTableChunk> table{};\n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

编译器给出以下错误:

\n
note: constraints not satisfied\nIn file included from /usr/include/c++/10/compare:39,\n                 from /usr/include/c++/10/bits/stl_pair.h:65,\n                 from /usr/include/c++/10/bits/stl_algobase.h:64,\n                 from /usr/include/c++/10/bits/char_traits.h:39,\n                 from /usr/include/c++/10/ios:40,\n                 from /usr/include/c++/10/ostream:38,\n                 from /usr/include/c++/10/iostream:39,\n                 from Minimal2.cxx:1:\n/usr/include/c++/10/concepts:57:15:   required for the satisfaction of \xe2\x80\x98__same_as<_Tp, _Up>\xe2\x80\x99 [with _Tp = int&; _Up = int]\n/usr/include/c++/10/concepts:62:13:   required for the satisfaction of \xe2\x80\x98same_as<int&, int>\xe2\x80\x99\n/usr/include/c++/10/concepts:57:32: note: the expression \xe2\x80\x98is_same_v<_Tp, _Up> [with _Tp = int&; _Up = int]\xe2\x80\x99 evaluated to \xe2\x80\x98false\xe2\x80\x99\n   57 |       concept __same_as = std::is_same_v<_Tp, _Up>;\n
Run Code Online (Sandbox Code Playgroud)\n

据我了解,thing.count_评估返回 anint&而不是 an int,这不是我所期望的。

\n

我应该测试吗{ thing.count_ } -> std::same_as<int&>?(然后编译。)这对我来说似乎相当违反直觉。

\n

use*_*522 19

如果是声明类型count_的成员,则表达式也是类型,并且表达式的值类别是左值thingintthing.count_int

表格的复合要求{ E } -> C将测试是否decltype((E))满足C。换句话说,它测试表达式的类型 E而不是可能命名的实体的类型)是否E满足概念。

通过获得的表达式的类型decltype((E))将值类别转换为类型的引用限定。右值导致非引用,左值导致左值引用,x值导致右值引用。

因此,在您的示例中,类型将是int&,但概念std::same_as需要类型的严格匹配,包括其引用限定,从而使其失败。


一个简单的解决方案是仅测试int&

{ thing.count_ } -> std::same_as<int&>;
Run Code Online (Sandbox Code Playgroud)

问题评论中也提到了类似的解决方案,自 C++23 起:

{ auto(thing.count_) } -> std::same_as<int>
Run Code Online (Sandbox Code Playgroud)

auto被推导为int,并且函数式强制转换表达式int(...)始终是纯右值,因此它永远不会产生引用限定类型。

另一种选择是编写一个概念来替换std::same_as,它不检查确切的类型相等性,而是首先将std::remove_reference_torstd::remove_cvref_t应用于类型,具体取决于您想要如何处理const-mismatch。值得注意的是,第一个解决方案不会接受const int成员或const- 具有T成员资格int,而第二个解决方案会(因为auto永远不会推导出 a const)。


但是,如果您打算检查 是否具有完全thing类型的成员,那么您应该小心。如果它具有引用类型的成员,则上述所有解决方案也将得到满足。int thingint

使用复合需求无法轻松排除参考案例,但可以使用嵌套需求来代替:

template <typename T>
concept HasCount = requires(T thing) {
    requires std::same_as<decltype(thing.count_), int>;
};
Run Code Online (Sandbox Code Playgroud)

嵌套要求(由另一个引入requires)不仅检查表达式的有效性,还检查它的计算结果是否为true。这里检查的区别在于我使用decltype(thing.count_)而不是decltype((thing.count_)). decltype当它直接通过成员访问表达式(不带括号)命名成员时会出现异常。在这种情况下,它将生成命名实体的类型,而不是表达式的类型。这验证了count_是 a int,而不是 a int&


T如果是const- 限定的还是引用类型,您还应该考虑其他边缘情况。在这些情况下,您应该仔细考虑在哪些条件下应该满足该概念。根据答案,建议的解决方案可能足够,也可能不够。


作为另一个边缘情况,您需要考虑您是否真的想接受任何成员或仅接受非static成员。上述所有解决方案都假设您static也接受会员。


此外,您还需要考虑是否应该接受从基类继承的成员。上面所有建议的解决方案都接受它们。


所有上述解决方案还假设仅当成员是 时才应该被接受public。概念中的检查是从与任何类都不相关的上下文中完成的,因此基于上下文的可访问性无论如何都不起作用,并且可能没有理由接受private实际上在函数中不可用的成员受观念的限制。