为什么被 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
}
所有主要编译器都认为这是一个错误:
gcc-trunk
:
<source>:12:33: required from here
<source>:7:5: error: forming reference to void
7 | maybe_void(const T& v) requires(!std::is_void_v<T>) {}
| ^~~~~~~~~~
clang-trunk
:
<source>:7:23: error: cannot form a reference to 'void'
maybe_void(const T& v) requires(!std::is_void_v<T>) {}
^
<source>:12:14: note: in instantiation of template class 'maybe_void<void>' requested here
auto v = maybe_void<void>(42);
^
msvc-v19-latest
:
<source>(7): error C2182: 'v': illegal use of type 'void'
<source>(12): note: see reference to class template instantiation 'maybe_void<void>' being compiled
据我了解,requires
是为了在上述情况下工作,但各种编译器似乎另有建议。为什么这不起作用?
注:
一种可能的解决方法是将构造函数更改为受约束的 template
:
template <typename U>
explicit maybe_void(const U& v) requires(!std::is_void_v<U> && std::same_as<T,U>);
虽然这是一种解决方法,但它并不能解释为什么 requires
子句不能阻止原本禁用的函数首先触发格式错误的问题。
问题是函数 signature 在语法上是无效的。 void const &
不是 C++ 中的合法类型。所以编译器永远不会到达函数的 requires
子句。它永远不会考虑该函数是否应该被丢弃,因为该函数不是合法的函数签名。
一般处理的方法是在类型中剔除void
。所以你需要一个单独的专业化,允许 void
。请注意,在大多数情况下,构造函数想要获取 T const&
,无论如何您都必须这样做,因为该函数几乎肯定会想要将 T
复制到成员变量或其他内容中。而且你不能有void
类型的成员变量。所以你仍然需要一个单独的专业。
如果您不需要专门化您的类型来处理 void
,那么您可以创建一个类型,当给定一个 T
即 void
时,会产生一个无害的非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>) {}
使用 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
}
所有主要编译器都认为这是一个错误:
gcc-trunk
:<source>:12:33: required from here <source>:7:5: error: forming reference to void 7 | maybe_void(const T& v) requires(!std::is_void_v<T>) {} | ^~~~~~~~~~
clang-trunk
:<source>:7:23: error: cannot form a reference to 'void' maybe_void(const T& v) requires(!std::is_void_v<T>) {} ^ <source>:12:14: note: in instantiation of template class 'maybe_void<void>' requested here auto v = maybe_void<void>(42); ^
msvc-v19-latest
:<source>(7): error C2182: 'v': illegal use of type 'void' <source>(12): note: see reference to class template instantiation 'maybe_void<void>' being compiled
据我了解,requires
是为了在上述情况下工作,但各种编译器似乎另有建议。为什么这不起作用?
注:
一种可能的解决方法是将构造函数更改为受约束的 template
:
template <typename U>
explicit maybe_void(const U& v) requires(!std::is_void_v<U> && std::same_as<T,U>);
虽然这是一种解决方法,但它并不能解释为什么 requires
子句不能阻止原本禁用的函数首先触发格式错误的问题。
问题是函数 signature 在语法上是无效的。 void const &
不是 C++ 中的合法类型。所以编译器永远不会到达函数的 requires
子句。它永远不会考虑该函数是否应该被丢弃,因为该函数不是合法的函数签名。
一般处理的方法是在类型中剔除void
。所以你需要一个单独的专业化,允许 void
。请注意,在大多数情况下,构造函数想要获取 T const&
,无论如何您都必须这样做,因为该函数几乎肯定会想要将 T
复制到成员变量或其他内容中。而且你不能有void
类型的成员变量。所以你仍然需要一个单独的专业。
如果您不需要专门化您的类型来处理 void
,那么您可以创建一个类型,当给定一个 T
即 void
时,会产生一个无害的非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>) {}