30

Background

Everybody agrees that

using <typedef-name> = <type>; 

is equivalent to

typedef <type> <typedef-name>; 

and that the former is to be preferred to the latter for various reasons (see Scott Meyers, Effective Modern C++ and various related questions on stackoverflow).

This is backed by [dcl.typedef]:

A typedef-name can also be introduced by an alias-declaration. The identifier following the using keyword becomes a typedef-name and the optional attribute-specifier-seq following the identifier appertains to that typedef-name. Such a typedef-name has the same semantics as if it were introduced by the typedef specifier.

However, consider a declaration such as

typedef struct { int val; } A; 

For this case, [dcl.typedef] specifies:

If the typedef declaration defines an unnamed class (or enum), the first typedef-name declared by the declaration to be that class type (or enum type) is used to denote the class type (or enum type) for linkage purposes only (3.5).

The referenced section 3.5 [basic.link] says

A name having namespace scope that has not been given internal linkage above has the same linkage as the enclosing namespace if it is the name of [...] an unnamed class defined in a typedef declaration in which the class has the typedef name for linkage purposes [...]

Assuming the typedef declaration above is done in the global namespace, the struct A would have external linkage, since the global namespace has external linkage.

Question

The question is now whether the same is true, if the typedef declaration is replaced by an alias declaration according to the common notion that they are equivalent:

using A = struct { int val; }; 

In particular, does the type A declared via the alias declaration ("using") have the same linkage as the one declared via the typedef declaration?

Note that [decl.typedef] does not say that an alias declaration is a typedef declaration (it only says that both introduce a typedef-name) and that [decl.typedef] speaks only of a typedef declaration (not an alias declaration) having the property of introducing a typedef name for linkage purposes. If the alias declaration is not capable of introducing a typedef name for linkage purposes, A would just be an alias for an anonymous type and have no linkage at all.

IMO, that's at least one possible, albeit strict, interpretation of the standard. Of course, I may be overlooking something.

This raises the subsequent questions:

  • If there is indeed this subtle difference, is it by intention or is it an oversight in the standard?
  • What is the expected behavior of compilers/linkers?

Research

The following minimal program consisting of three files (we need at least two separate compilation units) is used to investigate the issue.

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

Compiling this with gcc 7.2 with the "typedef" variant compiles cleanly and provides the expected output:

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

The compilation with the "using" variant produces a compile error:

> 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); ^~~~~ 

This looks like GCC follows the strict interpretation of the standard above and makes a difference concerning linkage between the typedef and the alias declaration.

Clang

Using clang 6, both variants compile and run cleanly without any warnings:

> 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 

One could therefore also ask

  • Which compiler is correct?
6
  • 9
    "If there is indeed this subtle difference, is it by intention or is it an oversight in the standard?" Here's a better question: who cares? The only reason to do typedef struct in C++ is for compatibility with C. And therefore, there's never a reason to do using A = struct..., whether it is legal or not. Commented Feb 4, 2018 at 23:30
  • Of course it results in an error, you have -pedantic-errors. Commented Feb 4, 2018 at 23:40
  • Linkage refers to function or variable, not struct or class. Really the first time I heard "linkage of a struct". en.cppreference.com/w/c/language/storage_duration#Linkage Commented Feb 4, 2018 at 23:43
  • 6
    @liliscent You're reading C documentation. What linkage means in C is different from what it means in C++. Commented Feb 5, 2018 at 0:05
  • 1
    @Nicolas Bolas. I agree 100% with you in saying that there is no reason to do using A = struct ... in C++. There is not even a reason to do typedef struct ... A unless you include headers from C or have your headers included from C. However, I do care, because I have seen people doing both (unnecessarily) and claiming they are equal. Commented Feb 5, 2018 at 6:10

2 Answers 2

12

This looks to me like a bug in GCC.

Note that [decl.typedef] does not say that an alias declaration is a typedef declaration

You're right, [dcl.dcl]p9 gives a definition of the term typedef declaration which excludes alias-declarations. However, [dcl.typedef] does explicitly say, as you quoted in your question:

2 A typedef-name can also be introduced by an alias-declaration. The identifier following the using keyword becomes a typedef-name and the optional attribute-specifier-seq following the identifier appertains to that typedef-name. It has the same semantics as if it were introduced by the typedef specifier. [...]

"The same semantics" doesn't leave any doubt. Under GCC's interpretation, typedef and using have different semantics, therefore the only reasonable conclusion is that GCC's interpretation is wrong. Any rules applying to typedef declarations must be interpreted as applying to alias-declarations as well.

Sign up to request clarification or add additional context in comments.

6 Comments

A typedef-name is one thing, a typedef declaration is another. An alias-declaration introduces a typedef-name, but is not a typedef declaration. The latter is defined very precisely in the standard as a declaration that contains the keyword typedef. There are many places in the standard that talk about "a typedef declaration or alias-declaration" (note hyphenation, italics, and usage of "a", I have copied them precisely from the draft standard).
@n.m. Huh, I missed the definition of the term "typedef declaration". Good point, I found it now. But there are many places that talk only of a typedef or a typedef declaration where it's beyond doubt that alias-declarations should be included, for instance [basic.types]p6, [dcl.fct]p10, [class.mem]p1. Will edit.
Perhaps not all of the standard was updated correctly to use alias-declaration when appropriate, but the intent to separate the two notions is clear.
@n.m. Yeah, agreed, so I removed that part from my answer, it doesn't affect my conclusion.
I've filed a GCC bug just in case: gcc.gnu.org/bugzilla/show_bug.cgi?id=99931
|
4

It looks like the standard is unclear on this.

On one hand,

[dcl.typedef] A typedef-name can also be introduced by an alias-declaration. [...] Such a typedef-name has the same semantics as if it were introduced by the typedef specifier.

On the other hand, the standard clearly separates the notions of typedef declaration and alias-declaration (the latter term is a grammar production name, so it is italicised and hyphenated; the former is not). In some contexts it talks about "a typedef declaration or alias-declaration", making them equivalent in these contexts; and sometimes it talks solely about "a typedef declaration". In particular, whenever the standard talks about linkage and typedef declarations, it only talks about typedef declarations and does not mention alias-declaration. This includes the key passage

[dcl.typedef] If the typedef declaration defines an unnamed class (or enum), the first typedef-name declared by the declaration to be that class type (or enum type) is used to denote the class type (or enum type) for linkage purposes only.

Note the standard insists on the first typedef-name being used for linkage. This means that in

typedef struct { int x; } A, B; 

only A is used for linkage, and B is not. Nothing in the standard indicates that a name introduced by alias-declaration should behave like A and not like B.

It is my opinion that the standard is insufficiently clear in this area. If the intent is to make only typedef declaration work for linkage, then it would be appropriate to state explicitly in [dcl.typedef] that alias-declaration does not. If the intent is to make alias-declaration work for linkage, this should be stated explicitly too, as is done in other contexts.

3 Comments

In a typedef-declaration that declares only a single typedef-name, which an alias-declaration is supposedly equivalent to, a typedef-name can only be the first typedef-name though.
@hvd We're trying to figure out whether we can treat alias-declaration as a kind of typedef declaration. Your reasoning seems to assume the very thing we're trying to establish.
No, that's not what I'm assuming, that's what's in your answer already. I was responding to the bit where you suggested that using B = struct { int x; }; could be treated as declaring B the way it's declared in typedef struct { int x; } A, B;.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.