如何阻止返回T& 的成员函数在T 等于void时被定义(并且应该如此)
我想为CUDA设备指针创建一个类似指针的类型包装器,但在定义 T& operator[] 时遇到了问题。默认情况下,编译器会抱怨我试图“从void形成引用”。因此我尝试使用C++20的概念来避免这个问题。但我得到同样的错误(示例都是普通的C++代码,不是CUDA,问题也一样):
#include <type_traits>
#include <concepts>
template<typename T>
struct TestPointerLike{
T* m_data;
[[nodiscard]]
T& operator[](std::integral auto i) const noexcept requires (!std::same_as<std::remove_cvref_t<T>, void>) {
return m_data[i];
}
};
int main() {
TestPointerLike<void> test;
}
<source>: In instantiation of 'struct TestPointerLike<void>':
<source>:14:27: required from here
14 | TestPointerLike<void> test;
| ^~~~
<source>:8:9: error: forming reference to void
8 | T& operator[](std::integral auto i) const noexcept requires (!std::same_as<std::remove_cvref_t<T>, void>) {
| ^~~~~~~~
Compiler returned: 1
请查看godbolt:
https://godbolt.org/z/aarrovhhv
我试图找出解决方法,看到这个问题:requires clause for a non-template member function in a template class
并以为它是在抱怨我以为可以工作的解决方案,但并没有起作用。这个答案也不起作用。我仍然遇到同样的问题。
#include <type_traits>
#include <concepts>
template<typename T>
struct TestPointerLike{
T* m_data;
[[nodiscard]]
T& operator[](std::integral auto i) const noexcept requires (!std::same_as<std::remove_cvref_t<T>, void>);
};
template <class T>
T& TestPointerLike<T>::operator[](std::integral auto i) const noexcept
requires (!std::same_as<std::remove_cvref_t<T>, void>){
return m_data[i];
}
int main() {
TestPointerLike<void> test;
}
<source>: In instantiation of 'struct TestPointerLike<void>':
<source>:18:27: required from here
18 | TestPointerLike<void> test;
| ^~~~
<source>:8:9: error: forming reference to void
8 | T& operator[](std::integral auto i) const noexcept requires (!std::same_as<std::remove_cvref_t<T>, void>);
| ^~~~~~~~
Compiler returned: 1
请参阅godbolt链接: https://godbolt.org/z/jWGeffqEx
而他们真正给出的第一种解决方案(我意识到他们把它发布成了一个解决方案,而不是一个问题)并没有奏效(与第一个godbolt链接的测试相同)。
目前为止,我发现真正可用的只有以下这一种:
#include <type_traits>
#include <concepts>
template<typename T>
struct TestPointerLike{
T* m_data;
template<typename U = T>
requires(!std::same_as<std::remove_cvref_t<T>, void> && std::same_as<T, U>)
[[nodiscard]]
U& operator[](std::integral auto i) const noexcept{
return m_data[i];
}
};
int main() {
TestPointerLike<void> test;
}
请看godbolt链接 https://godbolt.org/z/5YMTYYd43
有没有办法让它在大体上像我在第一个示例中想要的那样工作?
解决方案
Turns out there's a specialized facility for this: std::add_lvalue_reference_t<T>. It does nothing if forming a reference to T is illegal.
原来有专门的工具/机制来处理这个:std::add_lvalue_reference_t<T>。如果对 T 形成引用是非法的,它就不做任何事。
Alternatively, you could change the return type to something like std::conditional_t<std::is_void_v<T>, std::nullptr_t, T> &. When the condition is true, it'll resolve to some valid type (std::nullptr_t &), but the exact type doesn't matter, since the function will be disabled.
或者,你也可以将返回类型改为类似 std::conditional_t<std::is_void_v<T>, std::nullptr_t, T> & 的类型。当条件为真时,它会被解算为某个有效类型(std::nullptr_t &),但具体类型并不重要,因为该函数将被禁用。
Note that std::conditional_t<..., blah, T &> doesn't work, since both branches are instantiated regardless of the condition.
请注意,std::conditional_t<..., blah, T &> 不起作用,因为无论条件如何,两个分支都会被实例化。
Also, as I've done here, you could rewrite the requires condition to use std::is_void_v<T>. This automatically handles cv-qualified void too.
另外,正如我在这里所做的,你也可以将 requires 的条件改写为使用 std::is_void_v<T>。这也会自动处理带cv限定的 void。