C++20概念:如何在`requires`子句中引用类名?

19
我有一个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的地方就出现错误?

1
即使在大括号后面使用 static_assert(std::derived_from<T, Wrapper<T>>) 对于这种情况也不起作用,因为 T 是不完整的。https://godbolt.org/z/y-jVGZ - Justin
1个回答

5
不,这是不可能的。
目前这是一个语言问题——类名在代码中还未被编写时就不存在了。但即使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++版本的Russell悖论,它证明了不能使用定义自身的类型/集合来定义明确定义的类型/集合。

一个简单的问题: FooMe 的基类吗?也就是说,std::is_base_of_v<Foo,Me> 的值是什么?

  • 如果不是,那么Negator中的条件为真,因此Me继承自Negator<Me>::typeFoo,这是矛盾的。
  • 另一方面,如果它确实继承自Foo,我们发现它实际上并没有。

这似乎是一个人为的例子,而且确实如此,毕竟你最初问的是其他问题。

是的,可能有有限个段落可以添加到标准中,以允许您对Wrapper进行特定用途的使用,并禁止我对Negator的用法,但是必须在这些不太相似的例子之间划出一条非常细微的界线。

};之前需要早期的不完整性的另一个例子是sizeof的使用,这可能是更常见的论点:

  • sizeof(T) obviously depends on the size of all base classes. So using the expression inside a base class Wrapper of a derived type T that is still being written is another land mine waiting for you to step on.

  • Still, even easier example without any inheritance is:

    struct Me
    {
        int x[sizeof(Me)+1];
    };
    

    What is the size of Me?

"友元"技巧

我相信你在谈论的是Prevent user from deriving from incorrect CRTP base。是的,那个方法可行,但原因和你把requires放在方法附近一样。只有在实际生成它的调用时,即通常仅在创建实例时,检查被删除或无法访问的构造函数,此时Me才是完整的类型。

这也是有充分理由的。你希望这段代码能够工作:

struct Me
{
    int size(){
        return sizeof(Me);
    }
};

一个方法的存在不能影响Me的类型,因此这不会创建任何问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接