【问题标题】:using vs. typedef - is there a subtle, lesser known difference?使用与 typedef - 是否存在微妙的、鲜为人知的区别?
【发布时间】:2018-07-14 18:56:14
【问题描述】:

背景

每个人都同意

using <typedef-name> = <type>;

等价于

typedef <type> <typedef-name>;

并且由于各种原因,前者比后者更受欢迎(请参阅 Scott Meyers、Effective Modern C++ 和有关 stackoverflow 的各种相关问题)。

这是由 [dcl.typedef] 支持的:

typedef-name 也可以由别名声明引入。 using 关键字后面的标识符 成为 typedef-name 并且标识符后面的可选属性说明符序列属于该标识符 类型定义名称。这样的 typedef-name 具有与 typedef 说明符引入的语义相同的语义。

但是,考虑一个声明,例如

typedef struct {
    int val;
} A;

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

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

引用的第 3.5 节 [basic.link] 说

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

假设上面的 typedef 声明是在全局命名空间中完成的,那么结构 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);
}

海合会

使用带有“typedef”变体的 gcc 7.2 编译它可以干净地编译并提供预期的输出:

> 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

因此也可以问

  • 哪个编译器是正确的?

【问题讨论】:

  • "如果确实存在这种细微差别,是有意为之还是标准疏忽?" 这里有一个更好的问题:谁在乎? 在 C++ 中使用 typedef struct 的唯一原因是为了与 C 兼容。因此,永远没有理由使用 using A = struct...,无论它是否合法。
  • 当然会报错,你有-pedantic-errors
  • 链接指的是函数或变量,不是结构或类。真的是我第一次听到“结构的链接”。 en.cppreference.com/w/c/language/storage_duration#Linkage
  • @liliscent 您正在阅读 C 文档。链接在 C 中的含义与在 C++ 中的含义不同。
  • @Nicolas Bolas。我 100% 同意你的观点,即没有理由在 C++ 中使用 using A = struct ...。甚至没有理由这样做 typedef struct ... A 除非您包含来自 C 的标头或包含来自 C 的标头。但是,我确实很在意,因为我看到人们同时这样做(不必要地)并声称他们是平等的。

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


【解决方案1】:

这在我看来就像 GCC 中的一个错误。

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

你说得对,[dcl.dcl]p9 给出了术语 typedef 声明 的定义,其中不包括 alias-declaration。但是,正如您在问题中引用的那样,[dcl.typedef] 确实明确表示:

2 typedef-name 也可以由 alias-declaration 引入。 using 关键字后面的 identifier 成为 typedef-nameidentifier 后面的可选 attribute-specifier-seq em> 属于那个 typedef-name它的语义与 typedef 说明符引入的语义相同。 [...]

“相同的语义”毫无疑问。在GCC的解释下,typedefusing的语义不同,因此唯一合理的结论是GCC的解释是错误的。适用于 typedef 声明的任何规则都必须解释为也适用于别名声明。

【讨论】:

  • typedef-name 是一回事,typedef 声明是另一回事。别名声明引入了 typedef-name,但不是 typedef 声明。后者在标准中被非常精确地定义为包含关键字typedef 的声明。标准中有很多地方都在谈论“typedef 声明或 alias-declaration”(注意连字符、斜体和“a”的用法,我从标准草案中精确复制了它们)。
  • @n.m.呵呵,我错过了术语“typedef 声明”的定义。好点,我现在找到了。但是有很多地方只讨论 typedef 或 typedef 声明,毫无疑问应该包含别名声明,例如 [basic.types]p6、[dcl.fct]p10、[class.mem]p1。将编辑。
  • 也许并非所有标准都正确更新为在适当的时候使用别名声明,但区分这两个概念的意图很明确。
  • @n.m.是的,同意,所以我从答案中删除了那部分,这不影响我的结论。
  • 虽然说“这样的 typedef-name 与 typedef 说明符引入的语义相同”的段落让我感到困惑。如果它们具有相同的语义,为什么不把 alias-declaration 也称为 typedef 声明呢?
【解决方案2】:

这方面的标准似乎不清楚。

一方面,

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

另一方面,标准明确区分了typedef声明和alias-declaration的概念(后者是语法产生名称,所以它是斜体和连字符;前者不是) .在某些情况下,它谈到“typedef 声明或 alias-declaration”,使它们在这些情况下等价;有时它只谈论“typedef 声明”。特别是,每当标准谈到链接和 typedef 声明时,它只谈到 typedef 声明,而没有提及 alias-declaration。这包括关键段落

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

请注意,标准坚持使用 first typedef-name 用于链接。这意味着在

typedef struct { int x; } A, B;

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

我认为这方面的标准不够明确。如果意图只使 typedef 声明对链接起作用,那么在 [dcl.typedef] 中明确声明 alias-declaration 不适用是合适的。如果意图使 alias-declaration 用于链接,则也应明确说明这一点,就像在其他上下文中所做的那样。

【讨论】:

  • 在一个只声明一个 typedef-name 的 typedef-declaration 中,一个别名声明应该等同于它,但是 typedef-name 只能是第一个 typedef-name。
  • @hvd 我们试图弄清楚是否可以将 alias-declaration 视为一种 typedef 声明。你的推理似乎假设了我们正试图建立的东西。
  • 不,这不是我的假设,这就是您的答案已经包含的内容。我正在回复您建议 using B = struct { int x; }; 可以被视为声明 B 的那位,就像它在 typedef struct { int x; } A, B; 中声明的方式一样。
猜你喜欢
  • 2010-10-04
  • 2010-11-30
  • 2011-05-22
  • 2010-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多