转换运算符的外联定义的编译器差异
Compiler discrepancy for out-of-line definitions of conversion operators
对于以下代码是否格式正确,编译器之间似乎存在一些差异。
特别是,GCC 和 Clang 接受此代码——而 MSVC 拒绝它:
template <typename T>
class Bar{};
template <typename T>
struct Foo
{
using element_type = T;
operator Bar<element_type>();
};
template <typename T>
Foo<T>::operator Bar<element_type>()
{
return {};
}
MSVC 的拒绝消息只是一个通用的消息:
<source>(11): error C2065: 'element_type': undeclared identifier
只有当转换类型的模板参数是依赖模板类型时才会出现此问题,因为通过将 Bar<element_type>
更改为 Bar<T>
或 Bar<typename Foo<T>::element_type>
可以解决此问题。我在 compiler explorer 上做了一个小例子来证明这一点。这似乎发生在 C++11(可能较旧,未测试)到 C++2a 之间,并且与编译器的版本或标志无关。
我知道 C++ 允许在 class function/constructor 定义的正文或参数列表中删除类型的前缀 class-name 说明符 -- 但我不确定在外联定义中处理转换运算符时是否允许使用相同的缩写形式。
这是标准中的歧义,还是任一编译器中的错误?我很想知道这是如何定义的(或者不是,因为它可能be) 在 C++ 标准中。
编辑: 更让人困惑的是,MSVC 接受以下代码:
template <typename T>
struct Foo
{
using element_type = T;
operator element_type();
};
template <typename T>
Foo<T>::operator element_type() // note: no 'typename' syntax
{
return {};
}
所以这似乎不仅仅是转换为依赖于模板的类型名称引起的问题...我怀疑这可能是 MSVC 错误而不是差异。
标准在这个问题上非常模糊。如果您明确 调用 转换函数 (a.operator int()
),[basic.lookup.classref]/7 表示
If the id-expression is a conversion-function-id, its conversion-type-id is first looked up in the class of the object expression ([class.member.lookup]) and the name, if found, is used.
不幸的是,一个 conversion-type-id 可以有任意数量的 names(包括 0),在这种情况下是不可能的直接向上看。 [class.qual]/1.2 将相同的非规则应用于使用 ::
的函数引用,这可能适用于也可能不适用于此类函数的超出 class 的定义(因为正常查找不适用于此处)。
存在巨大的实施分歧。在下面的示例中,注释指示哪些编译器(Compiler Explorer 上目前的最新版本)拒绝哪些行(通常是因为找不到名称):
struct X {
struct A {};
operator A();
template<class> struct B {};
using C = int;
operator B<C>();
struct D {using I=int; I i;};
operator int D::*();
operator D::I();
static float E;
operator decltype(E)();
struct G {};
operator struct G();
};
X::operator A() {throw;} // OK
X::operator B<C>() {throw;} // MSVC: B and C
X::operator int D::*() {throw;} // MSVC
X::operator D::I() {throw;} // MSVC
X::operator decltype(E)() {throw;} // MSVC
X::operator struct G() {throw;} // MSVC thinks G is ::G
void f(X x) {
x.operator A(); // Clang, MSVC
x.operator B<C>(); // GCC, Clang, ICC: C; MSVC: B and C
x.operator int D::*(); // Clang
x.operator D::I(); // Clang
x.operator decltype(E)(); // Clang, ICC, MSVC; GCC segfaults
x.operator struct G(); // GCC, Clang, MSVC think G is local to f
}
Clang 接受所有定义并拒绝所有调用,因为它根本没有实现 [basic.lookup.classref]/7,而是错误地应用了 [basic.scope.class]/4 declarator-id 当它是 conversion-function-id.
New rules 正在澄清这一混乱局面(以及许多其他混乱局面);作为审查该论文的一部分,目前正在讨论上述究竟有多少应该被接受。我的意见是 GCC 的行为最接近期望的(当然除了 ICE 之外)。
对于以下代码是否格式正确,编译器之间似乎存在一些差异。 特别是,GCC 和 Clang 接受此代码——而 MSVC 拒绝它:
template <typename T>
class Bar{};
template <typename T>
struct Foo
{
using element_type = T;
operator Bar<element_type>();
};
template <typename T>
Foo<T>::operator Bar<element_type>()
{
return {};
}
MSVC 的拒绝消息只是一个通用的消息:
<source>(11): error C2065: 'element_type': undeclared identifier
只有当转换类型的模板参数是依赖模板类型时才会出现此问题,因为通过将 Bar<element_type>
更改为 Bar<T>
或 Bar<typename Foo<T>::element_type>
可以解决此问题。我在 compiler explorer 上做了一个小例子来证明这一点。这似乎发生在 C++11(可能较旧,未测试)到 C++2a 之间,并且与编译器的版本或标志无关。
我知道 C++ 允许在 class function/constructor 定义的正文或参数列表中删除类型的前缀 class-name 说明符 -- 但我不确定在外联定义中处理转换运算符时是否允许使用相同的缩写形式。
这是标准中的歧义,还是任一编译器中的错误?我很想知道这是如何定义的(或者不是,因为它可能be) 在 C++ 标准中。
编辑: 更让人困惑的是,MSVC 接受以下代码:
template <typename T>
struct Foo
{
using element_type = T;
operator element_type();
};
template <typename T>
Foo<T>::operator element_type() // note: no 'typename' syntax
{
return {};
}
所以这似乎不仅仅是转换为依赖于模板的类型名称引起的问题...我怀疑这可能是 MSVC 错误而不是差异。
标准在这个问题上非常模糊。如果您明确 调用 转换函数 (a.operator int()
),[basic.lookup.classref]/7 表示
If the id-expression is a conversion-function-id, its conversion-type-id is first looked up in the class of the object expression ([class.member.lookup]) and the name, if found, is used.
不幸的是,一个 conversion-type-id 可以有任意数量的 names(包括 0),在这种情况下是不可能的直接向上看。 [class.qual]/1.2 将相同的非规则应用于使用 ::
的函数引用,这可能适用于也可能不适用于此类函数的超出 class 的定义(因为正常查找不适用于此处)。
存在巨大的实施分歧。在下面的示例中,注释指示哪些编译器(Compiler Explorer 上目前的最新版本)拒绝哪些行(通常是因为找不到名称):
struct X {
struct A {};
operator A();
template<class> struct B {};
using C = int;
operator B<C>();
struct D {using I=int; I i;};
operator int D::*();
operator D::I();
static float E;
operator decltype(E)();
struct G {};
operator struct G();
};
X::operator A() {throw;} // OK
X::operator B<C>() {throw;} // MSVC: B and C
X::operator int D::*() {throw;} // MSVC
X::operator D::I() {throw;} // MSVC
X::operator decltype(E)() {throw;} // MSVC
X::operator struct G() {throw;} // MSVC thinks G is ::G
void f(X x) {
x.operator A(); // Clang, MSVC
x.operator B<C>(); // GCC, Clang, ICC: C; MSVC: B and C
x.operator int D::*(); // Clang
x.operator D::I(); // Clang
x.operator decltype(E)(); // Clang, ICC, MSVC; GCC segfaults
x.operator struct G(); // GCC, Clang, MSVC think G is local to f
}
Clang 接受所有定义并拒绝所有调用,因为它根本没有实现 [basic.lookup.classref]/7,而是错误地应用了 [basic.scope.class]/4 declarator-id 当它是 conversion-function-id.
New rules 正在澄清这一混乱局面(以及许多其他混乱局面);作为审查该论文的一部分,目前正在讨论上述究竟有多少应该被接受。我的意见是 GCC 的行为最接近期望的(当然除了 ICE 之外)。