Can typedefs in source files cause ODR violations?
Asked Answered
V

2

5

I've picked up the habit to always enclose types defined in source files in unnamed namespaces because I know they can cause ODR violations:

// my_source.cpp
namespace {
  struct MyStruct {};
}

What happens though in a source file that doesn't define a type, but merely aliases an existing one e.g.:

// another_source.cpp
using error_t = ::prj::telemetry::ErrorType;

could this create an ODR violation or cause weird linker behavior (e.g. discard one of the two types) if some other source contains:

// some_other_source.cpp
using errot_t = ::prj::linalg::ErrorType;

basically would the same rules applied to type definitions, also apply to typedefs?

Vile answered 11/12, 2023 at 16:15 Comment(0)
E
4

No (but actually more nuanced, see below). Typedefs like those in the question cannot cause ODR violations.

Firstly, typedefs (dcl.typedef in current standard draft) are usually declarations and not definitions. Even if you declare two typedefs in the form of those in the question in the same TU twice, that is a redeclaration error, and has nothing to do with ODR rule. If you don't have a definition of something, you can't possibly have a ODR violation for it. Note that both forms of typedef ... and using ... = ... are considered typedefs and fall under [dcl.typedef] in terms of the language specification. The relevant quotes about these typedefs not introducing new types are given here

A typedef-name does not introduce a new type the way a class declaration ([class.name]) or enum declaration ([dcl.enum]) does.

and here

Such a typedef-name has the same semantics as if it were introduced by the typedef specifier. In particular, it does not define a new type.

Secondly, ODR violation across different TU can only happen with names that have external linkage, or names with module linkage for TUs in the same module. In particular, names with no linkage cannot lead to cross-TU ODR violations. The cases for linkages are covered in basic.link from the latest standard draft, and in particular, the typedef declarations in the question falls under item 7,

Names not covered by these rules have no linkage. Moreover, except as noted, a name declared at block scope has no linkage.

Now here comes the more nuanced part. For the literal question in the title, the answer is actually yes, that is because we also have items 4.3 and 4.4 about typedefs to unnamed class types and unnamed enums, which introduce the concept of "typedef name used for linkage purposes".

The name of an entity that belongs to a namespace scope that has not been given internal linkage above and that is the name of

...

  • a named class ([class.pre]), or an unnamed class defined in a typedef declaration in which the class has the typedef name for linkage purposes ([dcl.typedef]);

  • a named enumeration, or an unnamed enumeration defined in a typedef declaration in which the enumeration has the typedef name for linkage purposes ([dcl.typedef]);

...

The actual definition is given in item 4 of dcl.typedef.

An unnamed class or enumeration C defined in a typedef declaration has the first typedef-name declared by the declaration to be of type C as its typedef name for linkage purposes ([basic.link]).

That means if we have the following code appearing in two different TUs, we have a ODR violation. And this is technically one form of typedef so it falls into the scope of the title of the question.

In the first TU:

typedef struct {} S, S1;

and in the second TU:

typedef struct {int x;} S, S2;
Ehf answered 11/12, 2023 at 23:3 Comment(0)
C
2

A typedef or a using statement does not introduce a new type, it just introduces an alias to another type, so no ODR violation is possible. From cppreference:

typedef

The typedef names are aliases for existing types, and are not declarations of new types

using

A type alias declaration introduces a name which can be used as a synonym for the type denoted by type-id. It does not introduce a new type and it cannot change the meaning of an existing type name

Centrepiece answered 11/12, 2023 at 16:20 Comment(7)
But can such type aliases be mixed up or end up designating the wrong type?Vile
If you create an alias and name it bob and there is already a type named bob then when you use bob like bob b; then you'll get an ambiguity error since it wont know which bob to use.Centrepiece
@LorahAttkins Or it'll give you a nice error that a type with that name already exists: coliru.stacked-crooked.com/a/2e1c79e5f114294e, coliru.stacked-crooked.com/a/4ae9724d64cde379Centrepiece
If there's two types named bob that have been defined in different translation units w/o namespace "protection" you open a can of worms even if those two sources don't have visibility/access/inclusion/dependency to each other. Can such a thing happen if two source files simply create an alias having the same name w/o an unnamed namespace ? (I'm not discussing the case where the type is visible to both declarations at all, that's straightforward )Vile
no, they alias is fine. And you can have two types names bob in two different TU's. It is only an ODR violation when more than one definition makes it into a TU.Centrepiece
I'd like to challenge the last point. C++ is not similar to C in this case: each TU can have its own definition (completely blind of the other) and the linker e.g. with LTO enabled can end up discarding or merging the two like in this example. I've had a case where one TU worked fine while the other produced code that called the wrong destructor (!) w/o the two TU ever talking to each other (or including related headers). The rule to "always define .cpp local types inside an unnamed namespace" remedies exactly thisVile
@LorahAttkins Yeah, my mistake, can't do that even if they are in separate TU's. You are still safe with the aliases as no new type is introduced.Centrepiece

© 2022 - 2024 — McMap. All rights reserved.