我有一个CRTP类(class)
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上进行了测试),但一件令人不满意的事情是,如果我从
Type
导出Wrapper<OtherType>
,则仅当我实例化Type
时才会引发错误。在Type
的定义时是否可能出现错误?
最佳答案
不,这实际上是不可能的。
现在这是一个语言问题-在将其实际写入代码之前,该类的名称不存在。但是,即使C++编译器多次读取文件并知道名称,仍然不够。允许这样做要么需要对类型系统进行重大更改而不是更好地进行更改,或者充其量只是一个非常脆弱的解决方案。让我解释。
假设如果可以在requires
子句中提及该名称,则该代码也将失败,因为T=Me
在这一点上仍然是不完整的类型。 @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
{
};
当然,这就是Russell's paradox的C++版本,它演示了不能使用自身定义良好定义的对象/集合。那么,
std::is_base_of_v<Foo,Me>
的值是什么?即Me
是从Foo
派生的吗?如果不是,则在这种情况下
Negator
类中的条件为true,因此Me
是从Negator<Me>::type
即Foo
导出的,这是一个矛盾。另一方面,如果它确实是从
Foo
派生的,我们发现它实际上不是。这似乎是人为的例子,毕竟您确实询问过其他问题。是的,您可以在标准中添加一定数量的段落,以允许
Wrapper
的特定用法,而不允许我使用Negator
,但是在这些不那么相似的示例之间必须划得很细的界限。在
};
打破sizeof
之前,需要尽早完成完整性,这可能是更常见的参数:sizeof(Me)
显然取决于所有基类的大小。因此,使用另一个仍在编写的基类中的表达式,该基类仍在编写中,因此无法通过定义来完成且没有大小,这是另一个地雷。struct Me
{
int x[sizeof(Me)];
};
“ friend 把戏”
我相信您在谈论this。是的,可以,但是出于同样的原因,您将
requires
放在可以使用的方法附近。仅当实际生成构造函数的调用时才检查被删除或不可访问的构造函数,通常只有在创建实例并且此时Me
是完整类型时才检查该构造函数。这样做也是有充分理由的,您希望此代码能够正常工作:
struct Me
{
int size(){
return sizeof(Me);
}
};
方法不能影响Me
的类型,因此不会造成任何问题。
关于c++ - C++ 20概念: how to refer to the class name in the `requires` clause?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62646616/