Фон
Все согласны с тем, что
using <typedef-name> = <type>;
эквивалентно
typedef <type> <typedef-name>;
и что первое должно быть предпочтительнее последнего по разным причинам (см. Scott Meyers, "Эффективное современное" C++ и различные связанные с ним вопросы о потоке stackoverflow).
Это подтверждается [dcl.typedef]:
Имя typedef также может быть введено с помощью объявления alias. Идентификатор, следующий за ключевым словом using, становится typedef-name и необязательным атрибутом-спецификатором-seq, следующим за идентификатором, к этому typedef-name. Такое имя typedef имеет такую же семантику, как если бы он был введен спецификатором typedef.
Однако рассмотрите декларацию, такую как
typedef struct {
int val;
} A;
Для этого случая [dcl.typedef] указывает:
Если декларация typedef определяет неназванный класс (или перечисление), первое имя typedef, объявленное объявлением как этот тип класса (или тип перечисления), используется для обозначения типа класса (или типа перечисления) только для целей привязки (3.5).
В упомянутом разделе 3.5 [basic.link] говорится
Имя с областью пространства имен, которая не была предоставлена внутренней связью выше, имеет ту же связь, что и охватывающее пространство имен, если оно является [...] неназванным классом, определенным в объявлении typedef, в котором класс имеет имя typedef для привязки целей [...]
Предполагая, что объявление typedef выше сделано в глобальном пространстве имен, структура A
будет иметь внешнюю связь, поскольку глобальное пространство имен имеет внешнюю связь.
Вопрос
Вопрос в том, является ли то же самое истинным, если объявление typedef заменяется объявлением псевдонима в соответствии с общим понятием, что они эквивалентны:
using A = struct {
int val;
};
В частности, имеет ли тип A
объявленный через объявление псевдонима ("использование"), те же связи, что и декларация, указанная в объявлении typedef?
Обратите внимание, что [decl.typedef] не говорит, что объявление псевдонима является объявлением typedef (оно только говорит, что оба вводят имя typedef), и что [decl.typedef] говорит только о объявлении typedef (а не об объявлении псевдонима), имеющем свойство введения имени typedef для целей связывания. Если объявление псевдонима не способно вводить имя typedef для целей привязки, A
будет просто псевдонимом анонимного типа и вообще не имеет привязки.
ИМО, по крайней мере, одну возможную, хотя и строгую, интерпретацию стандарта. Конечно, я, возможно, что-то пропускаю.
Это вызывает следующие вопросы:
- Если действительно есть эта тонкая разница, это по намерению или это надзор в стандарте?
- Каково ожидаемое поведение компиляторов/линкеров?
Исследование
Для исследования проблемы используется следующая минимальная программа, состоящая из трех файлов (нам нужны как минимум две отдельные единицы компиляции).
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 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
Поэтому можно было бы также спросить
- Какой компилятор прав?