使用vs. typedef - 是否有一个微妙的,鲜为人知的差异?

时间:2018-02-04 22:44:23

标签: c++11 gcc c++14 language-lawyer clang++

背景

每个人都同意

using <typedef-name> = <type>;

相当于

typedef <type> <typedef-name>;

并且前者因各种原因而优先于后者(参见Scott Meyers,Effective Modern C ++以及有关stackoverflow的各种相关问题)。

这由[dcl.typedef]支持:

  

也可以通过别名声明引入typedef-name。 using关键字后面的标识符   成为一个typedef-name和可选的attribute-specifier-seq,后面跟着标识符   typedef的名称。这样的typedef-name具有与typedef说明符引入的语义相同的语义。

但是,请考虑声明,例如

typedef struct {
    int val;
} A;

对于这种情况,[dcl.typedef]指定:

  

如果typedef声明定义了一个未命名的类(或枚举),那么第一个声明的typedef-name   声明为类类型(或枚举类型)用于表示链接的类类型(或枚举类型)   仅限用途(3.5)。

参考部分3.5 [basic.link]说

  

具有未命名的命名空间范围的名称   上面给出的内部链接与封闭命名空间具有相同的链接,如果它是名称   [...]   在类具有的typedef声明中定义的未命名类   用于链接目的的typedef名称[...]

假设上面的typedef声明是在全局命名空间中完成的,那么struct A将具有外部链接,因为全局命名空间具有外部链接。

问题

现在的问题是,如果将typedef声明替换为别名声明,根据它们是等效的常见概念,是否也是如此:

using A = struct {
    int val;
};

特别是,通过别名声明(“using”)声明的类型A与通过typedef声明声明的类型具有相同的链接吗?

注意[decl.typedef]并没有说别名声明 typedef声明(它只说两者都引入了typedef-name)而[decl.typedef]只说了一个 typedef声明(不是别名声明),具有为链接目的引入 typedef名称的属性。 如果别名声明无法为链接目的引入typedef名称,A将只是匿名类型的别名,并且根本没有链接。

IMO,这至少是对该标准的一种可能的,尽管是严格的解释。当然,我可能会忽略一些事情。

这引发了后续问题:

  • 如果确实存在这种微妙的差异,是意图还是 它是标准的疏忽吗?
  • 编译器/链接器的预期行为是什么?

研究

以下由三个文件组成的最小程序(我们至少需要两个独立的编译单元)用于调查此问题。

a.hpp

#ifndef A_HPP
#define A_HPP

#include <iosfwd>

#if USING_VS_TYPEDEF
using A = struct {
     int val;
};
#else
typedef struct {
     int val;
} A;
#endif

void print(std::ostream& os, A const& a);

#endif // A_HPP

a.cpp

#include "a.hpp"
#include <iostream>

void print(std::ostream& os, A const& a)
{
   os << a.val << "\n";
}

的main.cpp

#include "a.hpp"
#include <iostream>

int main()
{
    A a;
    a.val = 42;
    print(std::cout, a);
}

GCC

使用gcc 7.2对“typedef”变体进行编译,干净地编译并提供预期的输出:

> g++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=0 a.cpp main.cpp
> ./a.out
42

使用“using”变体进行编译会产生编译错误:

> g++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=1 a.cpp main.cpp
a.cpp:4:6: warning: ‘void print(std::ostream&, const A&)’ defined but not used [-Wunused-function]
void print(std::ostream& os, A const& a)
     ^~~~~
In file included from main.cpp:1:0:
a.hpp:16:6: error: ‘void print(std::ostream&, const A&)’, declared using unnamed type, is used but never defined [-fpermissive]
void print(std::ostream& os, A const& a);
     ^~~~~
a.hpp:9:2: note: ‘using A = struct<unnamed>’ does not refer to the unqualified type, so it is not used for linkage
};
 ^
a.hpp:16:6: error: ‘void print(std::ostream&, const A&)’ used but never defined
void print(std::ostream& os, A const& a);
     ^~~~~

这看起来像GCC遵循上述标准的严格解释,并对typedef和别名声明之间的联系产生了影响。

使用clang 6,两种变体都可以编译和运行,没有任何警告:

> clang++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=0 a.cpp main.cpp
> ./a.out
42

> clang++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=1 a.cpp main.cpp
> ./a.out
42

因此也可以问

  • 哪个编译器正确?

2 个答案:

答案 0 :(得分:10)

这对我来说就像GCC中的一个错误。

  

请注意,[decl.typedef]并未说别名声明是typedef声明

你是对的,[dcl.dcl] p9给出了术语 typedef声明的定义,它排除了 alias-declaration 。但是,正如您在问题中引用的那样,[dcl.typedef]明确地说:

  

2 alde-name 也可以通过 alias-declaration 引入。 using关键字后面的标识符变为 typedef-name 标识符后面的可选属性说明符-seq 附属于 typedef-name 它具有与typedef说明符引入的语义相同的语义。 [...]

&#34;相同的语义&#34;没有任何疑问。根据海湾合作委员会的解释,typedefusing具有不同的语义,因此唯一合理的结论是海湾合作委员会的解释是错误的。应用于typedef声明的任何规则也必须解释为应用于别名声明。

答案 1 :(得分:3)

看起来这个标准还不清楚。

一方面,

  

[dcl.typedef] alde-name 也可以通过 alias-declaration 引入。 [...]这样的 typedef-name 具有与typedef说明符引入的语义相同的语义。

另一方面,该标准明确区分了typedef声明和 alias-declaration 的概念(后一术语是语法生成名称,所以它是斜体和连字符;前者不是) 。在某些情况下,它会讨论&#34; typedef声明或 alias-declaration &#34;,使它们在这些上下文中等效;有时它只谈及&#34;一个typedef声明&#34;。特别是,每当标准讨论链接和typedef声明时,它只讨论typedef声明,而不提及 alias-declaration 。这包括关键段落

  

[dcl.typedef] 如果typedef声明定义了一个未命名的类(或枚举),则声明声明的第一个 typedef-name 是该类的类型(或枚举类型)用于表示链接的类类型(或枚举类型)   仅用途。

请注意,标准坚持使用第一个 typedef-name 进行链接。这意味着在

typedef struct { int x; } A, B;

A用于链接,B不用。标准中没有任何内容表明 alias-declaration 引入的名称应该像A一样,而不是B

我认为该标准在该领域尚不明确。如果目的是仅使typedef声明用于链接,那么在[dcl.typedef]中明确声明 alias-declaration 不会。如果目的是使 alias-declaration 用于链接,则应该明确说明,就像在其他上下文中一样。