我有一个 CRTP 课程
template <typename T>
class Wrapper
{
// ...
};
Run Code Online (Sandbox Code Playgroud)
旨在导出为
class Type : Wrapper<Type>
{
// ...
};
Run Code Online (Sandbox Code Playgroud)
我想通过对模板参数施加约束来强制执行T。有一个friend技巧可以做到这一点,但我认为在概念时代应该有更好的方法。我的第一次尝试是
#include <concepts>
template <typename T>
requires std::derived_from<T, Wrapper<T>>
class Wrapper
{
// ...
};
Run Code Online (Sandbox Code Playgroud)
但这不起作用,因为我指的是Wrapper在声明之前。我发现了一些不完全令人满意的解决方法。我可以将约束添加到构造函数
Wrapper() requires std::derived_from<T, Wrapper<T>>;
Run Code Online (Sandbox Code Playgroud)
但是如果我有更多的构造函数也必须受到约束,这就不方便了。我可以用析构函数来做
~Wrapper() requires std::derived_from<T, Wrapper<T>> = default;
Run Code Online (Sandbox Code Playgroud)
但是声明析构函数只是为了穿上它感觉有点傻requires。
我想知道是否有更好,更惯用的方法来做到这一点。特别是,虽然这些方法似乎有效(在 gcc 10 上测试过),但一件令人不满意的事情是,如果我Type从派生Wrapper<OtherType>,那么只有在我实例化 时才会引发错误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{};
Run Code Online (Sandbox Code Playgroud)
这当然只不过是罗素悖论的 C++ 版本,它证明了定义良好的类型/集合不能使用自身来定义。
一个简单的问题: 是Foo的基类吗Me?即 的值是多少std::is_base_of_v<Foo,Me>?
Negator为真,因此Me派生自Negator<Me>::typeie Foo,这是矛盾的。Foo,我们会发现它实际上不是。这可能看起来像是一个人为的例子,但事实确实如此,毕竟你确实问了别的事情。
是的,您可能可以在标准中添加有限数量的段落,以允许您的特定用法Wrapper并不允许我使用Negator,但必须在这些不太相似的示例之间划一条非常细的线。
之前需要早期不完整性的另一个例子};是它的用法sizeof可能是一个更常见的参数:
sizeof(T)显然取决于所有基类的大小。Wrapper因此,在仍在编写的派生类型的基类中使用表达式T是另一个等待您踩踏的地雷。
不过,没有任何继承的更简单的例子是:
struct Me
{
int x[sizeof(Me)+1];
};
Run Code Online (Sandbox Code Playgroud)
尺寸是多少Me?
我相信您正在谈论防止用户从不正确的 CRTP 基础派生。是的,这有效,但出于同样的原因,您将requires方法放在附近有效。仅当实际生成其调用时才会检查被删除或不可访问的构造函数,这通常仅在创建实例并且此时Me是完整类型时进行。
这样做也是有充分理由的,您希望此代码能够工作:
struct Me
{
int size(){
return sizeof(Me);
}
};
Run Code Online (Sandbox Code Playgroud)
方法的存在不会影响 的类型Me,因此这不会产生任何问题。