即使使用"requires(!std::is_void_v<T>)""Cannot form reference to void"错误



我正在编写一个指针类并重载解引用运算符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已知,就可以实例化类,但如果Tvoid,则由于成员函数签名,该实例化将失败。

您可以将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有效。

相关内容

最新更新