我正在编写一个指针类并重载解引用运算符operator*
,它返回对被指向对象的引用。当指向类型不是void时,这是可以的,但我们不能创建对void的引用,所以当指向类型是void时,我试图使用requires子句禁用operator*
。
然而,我仍然收到GCC、Clang和MSVC针对void情况的编译器错误,即使它不满足requires子句。
以下是一个最小的示例和编译器资源管理器链接(https://godbolt.org/z/xbo5v3d1E)。
#include <iostream>
#include <type_traits>
template <class T>
struct MyPtr {
T* p;
T& operator*() requires(!std::is_void_v<T>)
{
return *p;
}
};
int main() {
int x = 42;
MyPtr<int> i_ptr{&x};
*i_ptr = 41;
MyPtr<void> v_ptr{&x};
std::cout << *static_cast<int*>(v_ptr.p) << 'n';
std::cout << x << 'n';
return 0;
}
这是错误(Clang(:
<source>:7:6: error: cannot form a reference to 'void'
T& operator*()
^
<source>:20:17: note: in instantiation of template class 'MyPtr<void>' requested here
MyPtr<void> v_ptr{&x};
^
1 error generated.
ASM generation compiler returned: 1
<source>:7:6: error: cannot form a reference to 'void'
T& operator*()
^
<source>:20:17: note: in instantiation of template class 'MyPtr<void>' requested here
MyPtr<void> v_ptr{&x};
^
1 error generated.
Execution build compiler returned: 1
但是,如果我将operator*
的返回类型从T&
更改为auto&
,那么它在所有3个编译器中都有效。如果我使用尾随返回类型auto ... -> T&
,那么在所有3个编译器中也会出现错误。
这是三重编译器错误、用户错误还是预期行为?
requires
子句无关紧要,因为T
是类模板的参数。一旦T
已知,就可以实例化类,但如果T
是void
,则由于成员函数签名,该实例化将失败。
您可以将requires
放在整个类上,也可以使成员函数成为这样的模板:
template<typename U = T>
U& operator*() requires(!std::is_void_v<U> && std::is_same_v<T, U>)
{
return *p;
}
演示
制作返回类型auto&
几乎是一样的:返回类型是通过用虚类型模板参数U
替换auto
,然后进行模板参数推导得出的。请注意,如果您尝试将此函数与U=void
一起使用,则上面带有requires
的版本会清除编译错误:GCC表示template argument deduction/substitution failed: constraints not satisfied
。
我认为没有一种方法可以通过将函数作为模板来重现auto&
返回类型的确切功能。像这样的东西可能会接近:
template<typename U = T>
std::enable_if_t<!std::is_void_v<T>, U>& operator*()
{
return *p;
}
使用std::enable_if
(没有概念(将您正在尝试的内容与等效内容进行比较:
template<std::enable_if_t<!std::is_void_v<T>, bool> = true>
T& operator*()
{
return *p;
}
这将导致类似no type named 'type' in 'struct std::enable_if<false, bool>'
的错误,因为在T
不是函数模板的参数的情况下,SFINAE不起作用。
从技术上讲,您也可以根据T
是否为void
来更改返回类型,但这可能是个坏主意:
using R = std::conditional_t<std::is_void_v<T>, int, T>;
R& operator*()
{
// calling this with T=void will fail to compile
// 'void*' is not a pointer-to-object type
return *p;
}
除了Nelfeal的答案之外,让我给出一个替代解决方案。问题不在于requires
条件对T
的依赖性,而在于返回类型T&
。让我们使用一个助手类型的特性:
std::add_lvalue_reference_t<T> operator*()
requires(!std::is_void_v<T>)
{
...
}
它之所以有效,是因为std::add_lvalue_reference_t<void> = void
使operator*()
签名对T = void
有效。