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

时间:2020-06-29 21:02:10

标签: c++ c++-concepts

我有一个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上进行了测试),但一件令人不满意的事情是,如果我从Type派生Wrapper<OtherType>,则仅当我实例化Type时才会引发错误。在Type的定义点是否有错误?

1 个答案:

答案 0 :(得分:3)

不,这真的不可能。

现在这是一个语言问题-在实际将其写入代码之前,该类的名称不存在。但是,即使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类中的条件为真,因此Me源自Negator<Me>::typeFoo,这是一个矛盾。

另一方面,如果它确实来自Foo,我们发现它实际上不是。

这似乎是人为的例子,毕竟,您确实询问过其他问题。是的,您可以在标准中添加一定数量的段落,以允许Wrapper的特定用法,而不允许我使用Negator,但是必须画一条很细的线在这些不太相似的示例之间。

};打破sizeof之前需要尽早完整性,这可能是更常见的论点:

  • sizeof(Me)显然取决于所有基类的大小。因此,使用另一个仍在编写的基类内部的表达式,因此无法通过定义来完成且没有大小,这是另一个等待您继续前进的地雷。

  • 更简单的例子是:

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

“朋友把戏”

我相信您是在谈论this。是的,这可行,但是出于同样的原因,您将requires放在可行的方法旁边。仅当实际生成调用时才检查被删除或不可访问的构造函数,通常只有在创建实例并且此时Me才是完整类型时,才会对其进行检查。

这样做也是有原因的,您希望此代码能够正常工作:

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

方法不能影响Me的类型,因此不会造成任何问题。