我可以在周围作用域中声明一个成员类型别名,使用相同的名称吗?

12

我希望使用结构体来包含另一个类型的类型别名,以便进行元编程:

struct Foo {};

struct WithNestedTypeAlias {
    using Foo = Foo;
};

那么在模板中,我可以使用 WithNestedTypeAlias::Foo 这样的语法。

据我理解,这个类型别名是合法的,因为它没有改变 Foo 类型的含义。Clang 可以快乐地编译它。

然而,GCC 却报错:

test-shadow-alias.cpp:4:20: error: declaration of ‘using Foo = struct Foo’ [-fpermissive]
     using Foo = Foo;
                    ^
test-shadow-alias.cpp:1:8: error: changes meaning of ‘Foo’ from ‘struct Foo’ [-fpermissive]
 struct Foo {};
        ^

现在我感到困惑,因为我明确没有改变struct Foo的含义。

C++14的正确行为是什么? 我知道我可以通过重命名struct Foo来解决这个问题,但我想了解GCC的错误是否正确。

注:


1
将语句更改为 using Foo = ::Foo; 可以解决问题,但我无法解释为什么。 - François Andrieux
改为 using Foo = struct Foo; 也可以解决这个问题。请注意,等效的 typedef struct Foo Foo; 是惯用的 C 代码。 - Oktalist
1个回答

10

GCC正在执行的规则在[basic.scope.class]中:

2) 在类S中使用的名称N应在其上下文中引用相同的声明,并在S的完成作用域中重新评估时引用相同的声明。违反此规则不需要进行任何诊断。

标准规定违反此规则不需要进行诊断,因此GCC和Clang都符合标准,因为(如果GCC是正确的)代码无效,但编译器不需要诊断它。

此规则的目的是使在类中使用的名称始终表示相同的内容,重新排序成员也不会改变它们的解释方式。

struct N { };

struct S {
  int array[sizeof(N)];

  struct N { char buf[100]; };
};
在这个例子中,名称N的含义发生变化,并且重新排序成员会改变S :: array的大小。当定义S :: array时,N引用类型::N,但在完整作用域S中,它代替了S:: N。这违反了上述规则。
在您的示例中,名称Foo的变化方式要少得多,因为它仍然指的是同一类型,但严格来说,它确实从指向::Foo的声明变为指向S::Foo的声明。该规则以指向声明为措辞,因此我认为GCC是正确的。

嗯,那个规则确实有道理。但这似乎意味着我不应该使用 François Andrieux 在上面的评论中提到的 using Foo = ::Foo 技巧?虽然目前它可以工作,但它仍然是一个新的 Foo 声明,因此违反了你引用的规则,对吗?所以,这将让我只能使用 struct AnotherFoo{}; struct Nested { using Foo = AnotherFoo; }; using Foo = AnotherFoo; 这种方式来进行可移植的嵌套 Foo 声明。 - amon
3
在你原始代码中的问题在于右侧的Foo没有限定其作用域,因此它的含义发生了变化。在using声明之前,它的意思是全局的Foo,但稍后它会变成别名。如果将其限定作用域,则这个问题就不再存在了。 - Vaughn Cato
2
@amon 还请注意 CWG 42,它将回答你关于 Foo::Foo 和其他问题。 - bogdan
@amon 规则并不是 Foo 不能改变其含义,而是在其含义改变之前不能被使用。因此,using Foo = ::Foo 是可以的,因为未经限定的 Foo 在其含义改变之前没有被使用。同样地,如果我们将答案中的 sizeof(N) 替换为 1,那么这个例子也是可以的。 - Oktalist
@Oktalist 好的,我现在明白了。谢谢你的帮助 :) - amon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接