C++20 概念:如何在"requires"子句中引用类名?
C++20 concepts: how to refer to the class name in the `requires` clause?
我有一个 CRTP 类
template <typename T>
class Wrapper
{
// ...
};
旨在派生为
class Type : Wrapper<Type>
{
// ...
};
我想通过对模板参数T
施加约束来强制执行这一点。有一个friend
技巧可以做到这一点,但我认为在概念时代应该有更好的方法。我的第一次尝试是
#include <concepts>
template <typename T>
requires std::derived_from<T, Wrapper<T>>
class Wrapper
{
// ...
};
但这不起作用,因为我指的是Wrapper
在声明之前。我发现一些不完全令人满意的解决方法。我可以将约束添加到构造函数
Wrapper() requires std::derived_from<T, Wrapper<T>>;
但是如果我有更多的构造函数也必须受到限制,那就不方便了。我可以用析构函数做到这一点
~Wrapper() requires std::derived_from<T, Wrapper<T>> = default;
但是声明析构函数只是为了requires
它,感觉有点愚蠢。
我想知道是否有更好,更惯用的方法可以做到这一点。特别是,虽然这些方法似乎有效(在 gcc 10 上测试过(,但一件不令人满意的事情是,如果我从Wrapper<OtherType>
中派生Type
,那么只有在我实例化Type
时才会引发错误。在定义Type
时是否有可能出现错误?
不,这真的是不可能的。
现在这是一个语言问题 - 类的名称在实际写入代码之前不存在。但是,即使C++编译器多次读取文件并知道名称,仍然不够。允许这样做要么需要对类型系统进行重大更改,而不是为了更好,要么充其量是一个非常脆弱的功能。让我解释一下。
假设可以在requires
子句中提及该名称,则代码也会失败,因为此时T=Type
仍然是不完整的类型。 @Justin在他值得注意的评论中证明,我的回答是建立在这种基础上的。
但是,为了不让它在这里结束并成为一个非常无聊的"你不被允许这样做"的版本,让我们问问自己为什么Me
首先是不完整的?
看看下面这个相当人为的例子,就会发现在其基类中不可能知道完整的Me
类型。
#include <type_traits>
struct Foo;
struct Bar{};
template<typename T>
struct Negator {
using type = std::conditional_t<!std::is_base_of_v<Foo,T>, Foo, Bar>;
};
struct Me: Negator<Me>::type{};
当然,这只不过是罗素悖论C++版本,它证明了定义良好的类型/集合不能使用自身来定义。
一个简单的问题:Foo
是Me
的基类吗?即std::is_base_of_v<Foo,Me>
的价值是什么?
- 如果不是,则
Negator
中的条件为真,因此Me
是从Negator<Me>::type
派生的,即Foo
这是一个矛盾。 - 另一方面,如果它确实来自
Foo
,我们发现它实际上不是。
这似乎是一个人为的例子,毕竟你确实问了别的东西。
是的,您可能在标准中添加有限数量的段落以允许对您的Wrapper
的特定使用并禁止我对Negator
的使用,但是必须在这些不太相似的例子之间画一条非常细的线。
在};
之前需要早期不完整的另一个例子是sizeof
的使用,这可能是一个更常见的论点:
sizeof(T)
显然取决于所有基类的大小。因此,在派生类型的基类Wrapper
中使用仍在编写的表达式T
是另一个地雷等待您踩踏。不过,没有任何继承的更简单的例子是:
struct Me { int x[sizeof(Me)+1]; };
Me
的大小是多少?
"朋友把戏">
我相信您说的是防止用户从不正确的 CRTP 基数派生。是的,这有效,但出于同样的原因,您将requires
放在有效的方法附近。只有在实际生成其调用时,才会检查要删除或无法访问的构造函数,这通常仅在创建实例时进行检查,此时Me
是完整类型。
这样做也是有充分理由的,您希望此代码正常工作:
struct Me
{
int size(){
return sizeof(Me);
}
};
方法的存在不能影响Me
的类型,所以这不会产生任何问题。
- 将对象数组的引用传递给函数
- 什么时候在C++中返回常量引用是个好主意
- 我想将一个对T类型的非常量左值引用绑定到一个T类型的临时值
- 何时在引用或唯一指针上使用移动语义
- 如何在c++中使用引用实现类似python的行为
- 编译C++时未定义的引用
- Ctypes wstring通过引用传递
- c++r值引用应用于函数指针
- 理解c++中的引用
- C++取消引用指针.为什么会发生变化
- 如何修复此错误:未定义对"距离(浮点数,浮点数,浮点数,浮点数,浮点数)"的引用
- 我的项目不会像"undefined reference to `grpc::g_core_codegen_interface'"那样使用未定义的引用错误进行编译
- C++Boost Asio Pool线程,带有lambda函数和传递引用变量
- 强制转换为引用类型
- 引用一个已擦除类型(void*)的指针
- 向量元素的引用地址与它所指向的向量元素的地址不同.为什么
- 具有默认值的引用获取函数
- 如何使用基类指针引用派生类成员
- 使用取消引用的指针的多态性会产生意外的结果.为什么?
- 如何引用基类的派生类?