为什么被 C++20 `requires` 子句禁用的函数仍然会导致格式错误的类型错误?

Why do functions disabled by C++20 `requires` clauses still cause ill-formed type errors?

使用 C++20 的 requires 语句,我注意到使用 requires 有选择地禁用函数定义会中断,如果该函数中的类型格式不正确——甚至虽然该功能未启用。

我找到的最简单的例子是带有 T& 的任何东西,其中 T 可能是 void,例如:

template <typename T>
struct maybe_void {
    maybe_void() requires(std::is_void_v<T>) = default;

    maybe_void(const T& v) requires(!std::is_void_v<T>) {}
    explicit maybe_void(int) {};
};

auto test() -> void {
    auto v = maybe_void<void>(42); // error -- sees 'const void&' constructor, even though it's disabled
}

所有主要编译器都认为这是一个错误:

Live Example


据我了解,requires 是为了在上述情况下工作,但各种编译器似乎另有建议。为什么这不起作用?


注:

一种可能的解决方法是将构造函数更改为受约束的 template

template <typename U>
explicit maybe_void(const U& v) requires(!std::is_void_v<U> && std::same_as<T,U>);

Live Example

虽然这是一种解决方法,但它并不能解释为什么 requires 子句不能阻止原本禁用的函数首先触发格式错误的问题。

问题是函数 signature 在语法上是无效的。 void const & 不是 C++ 中的合法类型。所以编译器永远不会到达函数的 requires 子句。它永远不会考虑该函数是否应该被丢弃,因为该函数不是合法的函数签名。

一般处理的方法是在类型中剔除void。所以你需要一个单独的专业化,允许 void。请注意,在大多数情况下,构造函数想要获取 T const&,无论如何您都必须这样做,因为该函数几乎肯定会想要将 T 复制到成员变量或其他内容中。而且你不能有void类型的成员变量。所以你仍然需要一个单独的专业。

如果您不需要专门化您的类型来处理 void,那么您可以创建一个类型,当给定一个 Tvoid 时,会产生一个无害的非void类型。

struct dont_use {};

template<typename T>
struct non_void { using type = T; };

template<>
struct non_void<void> { using type = dont_use; };

...

maybe_void(non_void_t<T> const& v) requires(!std::is_void_v<T>) {}

As seen here.