可以针对不完整类型检查概念吗

Can a concept be checked against incomplete type

我偶然发现了这个:

#include <type_traits>
#include <concepts>

template<class T>
concept IsFoo = requires(T a)
{
    {a.a} -> std::same_as<int>;
};

#if 1
// Will not compile, because Foo currently has incomplete type
template<IsFoo AFoo>
struct AcceptsFoo
{};
#else
template<class AFoo>
struct AcceptsFoo
{};
#endif

struct Foo
{
    int a;
    int b;

    AcceptsFoo<Foo> obj;
};

https://gcc.godbolt.org/z/j43s4z

其他变体 (crtp) https://gcc.godbolt.org/z/GoWfhq

Foo 不完整,因为它必须实例化 AcceptsFoo,但要这样做,Foo 必须完整,否则无法检查 IsFoo。这是 GCC 中的错误,还是标准这么说?后者会令人难过,因为这会阻止概念与一些众所周知的模式(例如 CRTP)一起使用。

我注意到 clang 确实给出了类似的错误:https://gcc.godbolt.org/z/d5bEez

您可以检查一个概念是否符合不完整的类型——但如果该概念检查实际上需要对需要它完成的类型做任何事情,那么概念检查将失败.

即使在那里,您也必须小心,因为允许(并且将)实现缓存概念检查以更快地编译 - 所以如果您尝试 C<T>T 不完整并重试当 T 完成时,那些应该给出不同的答案,你是在自找麻烦。

Foo 在您检查它时还不完整,因此它自然没有名为 a 的成员。这个真的不行。

because this prevents concepts for being used together with some well-known patterns such as CRTP.

这样一起使用,可以。与使用 CRTP 一样,您也无法直接从传递到基 class 的模板参数访问任何内容,您始终必须小心延迟该类型的任何实例化,直到它完成。

这归根结底是同一个道理:

template <typename Derived>
struct B { 
    typename Derived::type x;
};

struct D : B<D> {
    using type = int;
};

无效。

[编辑] 这在 g++ 10.3、11.2 和当前的 clang 上按预期工作。一些评论表明这是未定义的行为,因此请注意未来编译器可能发生的意外更改。

原解:

我也偶然发现了这一点,并通过允许类型不完整或具有所需约束的完整,成功地使我的 CRTP with concepts 工作。

除了定义的 IsFoo,我还定义了 IsComplete 辅助函数(使用 sizeof 技巧),最后,IsFooIncomplete 定义如下:

template<class T> 
concept IsFooIncomplete = !IsComplete<T> || IsFoo<T>;

这样我可以确保,在 Foo 的处理过程中,它是不完整的,并且在 class 完成之后,它是完整的并且匹配所需的 IsFoo 约束。

#include <concepts>
#include <type_traits>

template<class T>
concept IsFoo = requires(T self)
{
   {
      self.a
      } -> std::same_as<int&>;
};

template<class T>
concept IsComplete = requires(T self)
{
   {
      // You can't apply sizeof to an incomplete type
      sizeof(self)
   };
};

template<class T>
concept IsFooIncomplete = !IsComplete<T> || IsFoo<T>;

#if 0
// Will not compile, because Foo currently has incomplete type
template<IsFoo AFoo>
struct AcceptsFoo
{};
#else
// will compile with IsFooIncomplete
template<IsFooIncomplete AFoo> // no need to use 'class AFoo' here...
struct AcceptsFoo
{};
#endif

struct Foo
{
   int a;
   int b;

   // Foo is incomplete here, but that's fine!
   static_assert(!IsComplete<Foo>);
   AcceptsFoo<Foo> obj;
};

// Foo is now complete, and that's also fine!
static_assert(IsFoo<Foo>);

g++ 版本 10.3.0 上工作正常(带有标志 --std=c++20),我希望它也能在其他编译器上工作。

[编辑] 正如评论中指出的那样,它接受任何不完整的类型,但这是有意的。只有外部静态断言会过滤完整的类型案例。感谢@David Herring 在 Bar 上的示例,我在这里写它进行测试:https://godbolt.org/z/sqc75qqMv


[EDIT2] 现在处理 CRTP 变体,没有任何未定义的行为,也没有 IsComplete 解决方法,只需存储类型以在 class 完成后进行检查。

// Will not compile, because Foo currently has incomplete type
template<IsFoo AFoo>
struct AcceptsFoo
{};
#else
template<class AFoo> // will not check IsFoo directly here...
struct AcceptsFoo
{
    using IsFooType = AFoo; // will store type on IsFootType for later checks
};
#endif

struct Foo : public AcceptsFoo<Foo> // will check only when complete
{
    int a;
    int b;
};

// Foo is now complete, and that's also fine!
static_assert(IsFoo<Foo::IsFooType>);

struct FooBar : public AcceptsFoo<FooBar> // will fail once it is complete
{
    //int a;
    int b;
};
// will fail here
static_assert(IsFoo<FooBar::IsFooType>);

这也适用于主要编译器:https://godbolt.org/z/e1Gc4Kj5n