One Definition Rule
The One Definition Rule (ODR) is a core language rule in the C++ programming standard that prohibits more than one definition of any variable, function, class type, enumeration type, template, or other definable item within a single translation unit, while requiring exactly one such definition for every non-inline, odr-used entity across the entire program to ensure consistent linking and behavior.[1]
This rule, detailed in section [basic.def.odr] of the ISO/IEC 14882 standard, applies to entities that are "odr-used," meaning they are referenced in a way that requires their address, value, or definition to be known at link time, such as in potentially-evaluated expressions or non-static data member initializers.[1] Violations of the ODR, such as providing differing definitions in multiple translation units, result in undefined behavior, and compilers are not required to issue diagnostics unless the entity is part of a named module with reachable prior definitions.[1]
Exceptions to the strict single-definition requirement allow multiple identical definitions for specific cases, including inline functions and variables (which must be defined in every translation unit where odr-used), class and enumeration types, and templated entities, provided all definitions appear in different translation units, consist of the same token sequence (ignoring certain whitespace and comments), resolve to the same entities via name lookup, and share the same language linkage.[1] For non-inline functions or variables with external linkage, any multiple definitions render the program ill-formed, potentially leading to linker errors or subtle runtime issues if undetected.
The ODR facilitates modular C++ development by enabling header files to declare but not fully define non-inline entities, while promoting the use of inline and template mechanisms to avoid duplication; adherence is enforced through practices like avoiding definitions in headers for non-inline items and using explicit instantiation for templates. Since its formalization in early C++ standards, the ODR has evolved to accommodate modules in C++20 and later, where definition domains (e.g., private module fragments) further refine reachability and odr-usability constraints.[1]
Overview
Definition and Scope
The One Definition Rule (ODR) is a fundamental constraint in the C++ programming language, as specified in the standard clause [basic.def.odr]. It requires that no translation unit shall contain more than one definition of any definable item, including variables, functions, class types, enumeration types, concepts (since C++20), and templates. Multiple declarations of these entities are permitted within a single translation unit or across translation units, but definitions—those that fully specify the entity's implementation or initial value—must adhere strictly to this uniqueness.[2] For non-inline functions and variables that are odr-used (i.e., their names appear in a potentially-evaluated context that requires the definition), exactly one definition must exist in the entire program, with undefined behavior resulting from violations, though compilers are not required to diagnose them.
The scope of the ODR encompasses a broad set of program entities to ensure consistent linkage and behavior across the compilation model. It applies to non-inline functions and variables, which must have a single definition program-wide; class and enumeration types, which permit one definition per translation unit but require identical definitions across units; and templates, including their specializations, which follow similar per-unit uniqueness with linkage consistency.[2] Concepts, introduced in C++20, are also subject to the ODR, treating them as templated entities that demand unique definitions within translation units. The rule explicitly distinguishes declarations from definitions: a declaration introduces a name but does not necessarily provide a complete description (e.g., a function prototype or forward class declaration), whereas a definition does (e.g., a function body or class body with members).[2]
A translation unit serves as the foundational scope for ODR enforcement and is defined as the output of the preprocessing phase applied to a single source file, including all included header files and macro expansions, resulting in a sequence of tokens ready for compilation. This unit boundary is critical because the ODR allows certain entities (like types and inline functions) to be defined in every translation unit where needed, provided the definitions are identical in token sequence and semantics across the program.[2]
The ODR has evolved across C++ standards to accommodate language extensions while preserving linkage integrity. It was first formalized in C++98 for core entities like variables, functions, classes, enumerations, and templates. C++11 refined it to support constexpr functions and variables, allowing their use in constant expressions without violating uniqueness when odr-used at compile time. C++17 introduced inline variables, permitting their definition in every translation unit where odr-used, similar to inline functions, to facilitate header-only libraries. In C++20, the addition of concepts and modules further adjusted ODR application: concepts follow template-like rules, while modules enable explicit import of definitions, reducing implicit inclusion risks and enhancing ODR compliance through named modules and export controls.
Purpose and Importance
The One Definition Rule (ODR) serves as a cornerstone of C++'s separate compilation model, allowing developers to declare entities like functions, variables, classes, and templates in header files for reuse across multiple translation units while requiring exactly one definition per entity in the entire program. This design enables modular programming by separating declarations from definitions, avoiding code duplication and promoting maintainability in large projects. Without the ODR, including definitions in headers would lead to multiple identical copies during linking, potentially causing conflicts or inefficiencies.[2]
The rule's importance lies in its role during the linking phase, where it ensures the linker resolves a single, consistent definition for each odr-used entity, preventing errors from mismatched or duplicate implementations. In multi-file projects and libraries, ODR violations can produce undefined behavior, such as subtle runtime discrepancies if compilers optimize based on differing views of the same entity across translation units. By bridging independent compilation of translation units with whole-program assembly, the ODR supports scalable builds and reliable integration of components.[2]
Historically, the ODR evolved to address C's more permissive approach, where compatible type definitions could vary across files; C++'s enhanced type safety and features like classes demanded program-wide consistency to avoid inconsistencies in object layouts or semantics. This stricter enforcement facilitated C++'s growth from C, enabling optimizations and features that assume a unified entity view.[2]
In modern C++, the ODR is particularly vital for C++20 modules, which impose stricter compliance by tying definitions to module purviews and interfaces, thereby minimizing violation risks inherent in traditional header inclusions and improving encapsulation and compilation speed.[3]
Core Principles
The Basic Rule
The One Definition Rule (ODR) in C++ prohibits multiple definitions of certain program entities within the same translation unit and imposes strict uniqueness requirements across the entire program. Specifically, no translation unit shall contain more than one definition of any non-inline function, non-inline variable, class type, or enumeration type.[1] For non-inline functions and variables that are odr-used in the program, there must be exactly one definition across all translation units; additional definitions in other units render the program ill-formed.[2] This ensures that the linker can unambiguously resolve references without conflicts, maintaining consistent behavior throughout the executable.
Class types and enumeration types follow a per-translation-unit scope for definitions, but any such definitions appearing in multiple translation units must be identical in a precise manner to satisfy the ODR. Equivalence requires that the definitions have the same sequence of tokens, that name lookup and overload resolution in each yield the same entities, and that attributes like language linkage and constant initialization match exactly.[1] For instance, reordering member functions in a class definition or altering the underlying type of an enumeration across units would violate this semantic equivalence, even if the overall structure appears similar. Pure declarations, such as extern int x;, do not constitute definitions and thus fall outside the ODR's prohibitions.[2]
Prior to C++11, the ODR imposed stricter requirements on static constant integral data members of classes, mandating an out-of-class definition if the member was odr-used, despite allowing in-class initialization.[2] C++17 and later standards relaxed this by making constexpr static data members implicitly inline, permitting their definition solely within the class without additional out-of-class declarations even if odr-used. These changes in [basic.def.odr] paragraphs 3 through 6 emphasize the distinction between program-wide uniqueness for callable and object entities versus translation-unit consistency for types, preventing subtle linkage errors in multi-file programs.[1]
ODR-Usage Concept
The One Definition Rule (ODR) in C++ applies specifically to entities that are "odr-used" within a program, meaning their definitions must be identical and appear exactly once (for non-inline entities) across all translation units where they are used. An entity is odr-used if it is referenced in a way that requires the compiler or linker to access its full definition, rather than merely its declaration or type information. This concept ensures that the program's semantics remain consistent despite separate compilation, preventing issues like multiple conflicting definitions from causing undefined behavior.[1]
Odr-use can be direct, such as when a function is explicitly called (e.g., f()) or a variable's address is taken (e.g., &x), or indirect, such as through virtual function dispatch or implicit invocations like copy constructors in initialization. For variables, odr-use occurs if the variable is named by a potentially-evaluated expression that reads or writes its value, binds a reference to it (unless it's a compile-time constant), or uses it in certain constant expressions requiring lvalue-to-rvalue conversion. Functions are odr-used if named by a potentially-evaluated expression or conversion, including non-pure virtual functions or allocation/deallocation functions invoked by constructors and destructors. Additionally, entities like structured bindings (since C++17) or the implicit this pointer are odr-used in potentially-evaluated contexts.[2][4][5]
Not all references to an entity constitute odr-use. Pure declarations, such as forward declarations without any evaluation, do not trigger it. Type-only uses, like declaring a pointer to an incomplete type (e.g., T* p; without defining T), or tentative definitions (e.g., int x;) in contexts where the entity is not evaluated, are non-odr-uses. Unevaluated contexts, such as the operand of sizeof or alignof, or discarded-value expressions without lvalue-to-rvalue conversion (e.g., S::x; where the value is ignored), also avoid odr-use for variables usable in constant expressions.[2][4]
The precise rules for odr-use are detailed in sections [basic.def.odr]/4 through /10 of the C++ standard. Paragraph 4 addresses functions named in overload resolution or explicit qualifications; paragraph 5 covers variables in potentially-evaluated expressions; paragraphs 6-7 extend to structured bindings and this; and paragraphs 8-9 specify function odr-use, including implicit cases for constructors and destructors. C++11 introduced refinements for lambdas, where closure types are considered equivalent across translation units if their lambda-expressions have matching token sequences, and for constexpr functions, which may require instantiation if named in potentially constant-evaluated contexts. Local entities have additional odr-usability constraints in nested scopes to prevent ill-formed programs.[6][7]
A key implication of the odr-use concept is that entities which are never odr-used—such as unused tentative definitions—can appear multiply across translation units without violating the ODR, as long as they are not linked or evaluated. This allows flexibility in header files for declarations that might not be utilized in every unit, but it underscores the need for careful design to avoid accidental odr-use triggering multiple definitions.[2][1]
Exceptions and Special Cases
Inline Functions and Variables
The One Definition Rule (ODR) permits an exception for inline functions, allowing their definitions to appear in multiple translation units without violating the rule, provided all such definitions are identical in terms of token sequence, name lookup results, and language linkage.[8] This exception facilitates the inclusion of inline function definitions in header files, which are then incorporated into every translation unit that includes the header.[9] In contrast to non-inline functions, which must have exactly one definition across the entire program if odr-used, inline functions support this multiplicity to enable optimizations like inlining while maintaining program correctness.[8]
The mechanics of this exception rely on the inline specifier granting the function external linkage by default, with the linker responsible for selecting or merging the definitions into a single instance during the linking phase.[9] In practice, this is achieved through weak symbols or COMDAT sections (on platforms like Windows), ensuring that duplicate definitions do not result in multiple instances but are unified into one, with shared behavior for function-local statics and types.[9] An odr-use of an inline function, such as a call or taking its address, requires its definition to be present in every translation unit where it occurs, but the program behaves as if there is only one definition if the ODR conditions are met.[8]
Introduced in C++17, the inline specifier was extended to variables with static storage duration, allowing multiple identical definitions of inline variables across translation units, similar to inline functions.[9] This feature is particularly useful for header-only libraries, enabling declarations like inline static const int x = 5; without requiring external declarations or risking ODR violations.[9] Inline variables must have external linkage (default for namespace-scope inline const variables) and identical initializers; their initialization is deferred until the first odr-use in the program.[9]
Key requirements for compliance include ensuring that all definitions are lexically identical and that the inline entity is odr-used consistently, though it must ultimately behave as a single definition per program.[8] In header-only scenarios, avoiding odr-use within the header itself prevents the need for a single program-wide definition.[8] However, if definitions differ—even subtly, such as in default arguments or constant values—the program exhibits undefined behavior, with no requirement for diagnostics from the compiler or linker.[8] This exception does not apply to class types, which follow separate ODR rules for their definitions.[8]
Templates and Explicit Specializations
In C++, the One Definition Rule (ODR) applies to templates by permitting a single definition of a template in each translation unit, provided that all such definitions across multiple translation units are equivalent. Equivalence requires that the definitions consist of the identical sequence of tokens and that name lookup within each definition resolves to the same entities, ensuring consistent behavior during compilation and linking.[2] This rule extends to both class templates and function templates, where the primary template's definition must match exactly in form and semantics wherever it appears, typically through inclusion of the same header file.
Template instantiation under the ODR involves implicit and explicit mechanisms, each with specific constraints. For implicit instantiations, the compiler generates the specialization from the primary template (or the most specialized partial specialization if applicable) in each translation unit where the template is odr-used, relying on the equivalent primary definition to avoid discrepancies.[2] Explicit specializations, however, are generated manually and must adhere to stricter rules: if defined in multiple translation units, they require identical definitions to maintain equivalence, but in practice, they are often declared in headers and defined once to prevent violations.[10] Partial specializations follow the template equivalence model, allowing one definition per translation unit with the condition that all versions across units match in token sequence and name resolution; the compiler selects the most specialized matching partial specialization during instantiation, falling back to the primary if none apply.[11]
Explicit specializations of templates are treated as non-template entities under the ODR, meaning there can be only one definition of such a specialization in the entire program, regardless of translation units.[10] Unlike primary templates, explicit specializations cannot be implicitly instantiated and must be defined after the primary template's declaration but before their first odr-use to suppress unwanted implicit generation.[10] They do not inherit inline, constexpr, or similar qualifiers from the primary template; instead, these attributes are determined solely by the specialization's own declaration—for instance, an explicit specialization can be marked inline to allow multiple definitions in different translation units, but without this, duplication leads to undefined behavior.[10] For example, the following code demonstrates a valid explicit specialization:
template<typename T> void f(T) { /* primary template body */ }
template<> void f(int) { /* explicit specialization; defined once program-wide */ }
template<typename T> void f(T) { /* primary template body */ }
template<> void f(int) { /* explicit specialization; defined once program-wide */ }
If this specialization were defined differently in another translation unit, it would violate the ODR.[10]
Introduced in C++20, modules impose stricter ODR enforcement for templates and their specializations through module interface units, which serve as the unique point of definition for exported entities.[3] Unlike traditional header inclusions that can lead to multiple equivalent definitions, module imports reference a single module interface unit per named module, preventing inadvertent duplication and ensuring templates are instantiated from a unified source.[3] This modular approach attaches template definitions to their module's linkage, making redefinitions across different modules ill-formed and thus enhancing ODR compliance by design.[3] For instance, a template defined in a module interface unit is exported once and imported without redefinition in consuming modules, avoiding the token equivalence checks required in pre-C++20 header-based systems.[3]
Equivalence issues in templates often arise from subtle differences in name lookup across translation units, even when token sequences appear identical, potentially causing undefined behavior.[2] For example, if a dependent name in a template definition resolves to different entities due to varying enclosing scopes or macro expansions in different units, the resulting instantiations may differ, violating ODR despite superficial similarity.[2] Such problems are particularly prevalent in templates with dependent names, where lookup occurs at the point of instantiation rather than definition, amplifying the risk if the primary template definitions are not perfectly synchronized. To mitigate this, best practices include centralizing template definitions in a single header and using forward declarations judiciously, ensuring consistent name resolution program-wide.[2]
Static Const Data Members
In C++, static constant data members of a class are subject to the One Definition Rule (ODR), which requires that they have at most one definition across the entire program if odr-used, while allowing identical declarations in multiple translation units.[2] For static const members, the rules for definitions have evolved across C++ standards to balance usability and linkage requirements. Prior to C++11, static const integral or enumeration type data members could be initialized and effectively defined in-class without an out-of-class definition, provided they were only used in constant expressions and not odr-used in ways that required storage, such as taking their address or using them in non-constant contexts.[12] This in-class definition was sufficient because such members did not require separate linkage; however, if odr-used (e.g., via a pointer or reference beyond constant evaluation), an explicit out-of-class definition in exactly one translation unit was mandatory to provide the storage, ensuring ODR compliance.[2]
With C++11, the rules tightened for broader applicability: all static data members, including const ones, require an out-of-class definition if odr-used, regardless of type, as in-class initializers alone do not constitute a full definition for linkage purposes unless the member is constexpr (introduced in C++11).[12] A constexpr static data member must be initialized in-class with a constant expression, and this serves as its definition, implicitly making it inline and exempt from needing an out-of-class counterpart, even if odr-used.[13] For non-constexpr static const members, odr-use—such as reading the value in a non-constant context or passing its address—triggers the need for a single out-of-class definition (e.g., const int ClassName::memberName;) in one translation unit, while identical in-class declarations are permitted elsewhere to avoid multiple definitions.[2] Failure to provide this can lead to linker errors or undefined behavior if the member is odr-used without a definition.
C++17 further simplified handling with the inline static specifier, allowing static data members (const or otherwise) to be defined in-class and included in every translation unit where the class is defined, as long as all such definitions are identical in content and form.[12] This extension to static const members eliminates the previous requirement for out-of-class definitions in most cases, treating them like inline functions under the ODR: multiple identical definitions are merged at link time into one.[2] However, the in-class initializer still only counts as a definition if the member is odr-used; otherwise, no explicit definition is needed at all. This change reduces common pitfalls in header-only libraries but maintains ODR by prohibiting differing definitions across units.
Under the ODR, multiple in-class declarations of a static const data member are permissible if identical, but odr-use demands exactly one program-wide definition to avoid undefined behavior, such as when definitions differ between a header and a source file, potentially causing linker conflicts or multiple symbol instances.[2] For instance, consider a class with static const [int](/page/INT) value = [42](/page/42); in its header; if odr-used like const [int](/page/INT)* ptr = &Class::value;, a pre-C++17 program requires an out-of-class const [int](/page/INT) Class::value; in one .cpp file, or the linker may fail to resolve the symbol.[12] In C++17, adding inline (e.g., inline static const [int](/page/INT) value = [42](/page/42);) resolves this by allowing the in-class definition to suffice everywhere, ensuring a single effective definition post-linking.[12]
Violations and Consequences
Detectable Violations
Detectable violations of the One Definition Rule occur within a single translation unit, where the compiler can identify and diagnose multiple or conflicting definitions of the same definable item during the compilation process. According to the C++ standard, no translation unit shall contain more than one definition of any definable item, such as a variable, function, class type, enumeration type, or template; violations of this intra-unit constraint render the program ill-formed, requiring the compiler to issue a diagnostic.[14] This mandate ensures that basic consistency is enforced at the compilation stage, preventing obvious errors before linking.
Common detectable cases include duplicate definitions of classes or non-identical inline functions within the same translation unit. For example, declaring and defining a class type twice with differing members, such as an empty struct followed by one containing an integer member, results in a compiler error due to the conflicting definitions violating the single-definition requirement.[14] Similarly, providing multiple bodies for a non-inline function or differing implementations for an inline function in one unit triggers a diagnostic, as the standard prohibits such multiplicity and mandates identical semantics where applicable.[14] For odr-used variables or functions lacking a definition in the translation unit where they are required, the compiler may also detect and report the issue if the usage demands a complete definition at compile time, though the standard does not always require a diagnostic for missing definitions across the entire program.[15]
Compilers typically respond to these violations with errors for clear conflicts, such as mismatched types or multiple non-inline definitions, and may issue warnings for subtler issues like potential odr-usage without resolution. The C++20 standard introduces enhanced module-specific checks, mandating diagnostics for ODR mismatches only when a definable item is attached to a named module and a prior definition is reachable at the point of conflict, thereby improving detectability in modular codebases.[16] Tools like include guards in header files help prevent accidental multiple inclusions that could lead to intra-unit violations, but they do not mitigate inter-unit ODR issues or excuse non-compliance with the rule's broader requirements.
Undetectable Violations and Undefined Behavior
Undetectable violations of the One Definition Rule (ODR) occur when non-identical definitions of entities such as functions, variables, or types exist across different translation units, and the compiler or linker fails to detect the discrepancy. According to the C++ standard, such programs are ill-formed, NDR (no diagnostic required), resulting in undefined behavior without any obligation for the implementation to issue a warning or error. For instance, if an inline function has differing bodies in separate source files—such as one version performing a computation and another omitting it—the linker may select an arbitrary implementation, leading to semantically incorrect code that appears to link successfully.[1]
The consequences of these violations can manifest as program crashes, incorrect computational results, or subtle runtime bugs that are difficult to reproduce or debug, since the standard does not specify how the implementation must behave in the presence of such discrepancies. Even though only one definition may be linked into the final executable, the chosen one might not match the expectations from other units, potentially causing type mismatches or incompatible function semantics. This unpredictability arises because traditional compilation separates translation units, preventing cross-unit equivalence checks unless advanced techniques are employed.[2]
Modern compilers like GCC and Clang can detect some inter-unit ODR violations through Link-Time Optimization (LTO), which performs whole-program analysis by optimizing across object files. For example, enabling LTO with flags such as -flto allows these tools to compare definitions and report mismatches, though detection is not guaranteed for all cases, such as when symbols are weakly defined or in certain template instantiations.[17]
In C++20, the introduction of modules provides stronger safeguards against undetectable ODR violations. When a definable item is attached to a named module interface and a prior definition is reachable via import, the standard requires a diagnostic if the definitions differ, thereby reducing the risk of silent undefined behavior in modular codebases.[1]
To mitigate these issues, best practices include declaring non-inline functions and variables in header files while providing their definitions in a single translation unit (e.g., a .cpp file), and restricting header-only definitions to inline functions or templates where the ODR explicitly permits multiple identical instances. This approach ensures consistency across units without relying on optional compiler features.[2]
Practical Examples
Compliant Multiple Definitions
The One Definition Rule (ODR) permits multiple definitions of certain entities across translation units provided they are identical in token sequence, name lookup, and other specified attributes, ensuring no observable difference in program behavior.[1] This exemption applies to inline functions, templates, and specific static members, allowing their definitions in header files included in multiple source files without violating the rule, as the linker merges identical copies.[1]
An inline function can be defined identically in a header file and included across multiple translation units; the compiler generates code in each unit, but the linker discards duplicates during final linking. For instance, consider a header utils.h:
cpp
inline int add(int a, int b) {
return a + b;
}
inline int add(int a, int b) {
return a + b;
}
This header is included in file1.cpp and file2.cpp, both using add(1, 2). Each translation unit sees the same definition, satisfying ODR requirements for identical tokens and linkage.[1] The resulting executable contains a single merged instance.[18]
Template definitions, including classes, may appear in headers and be instantiated equivalently in different translation units, as templates are not instantiated until used and multiple identical definitions are permitted if they match exactly. Consider a header vector.h:
cpp
template<typename T>
class SimpleVector {
T data[10];
public:
T& operator[](size_t i) { return data[i]; }
};
template<typename T>
class SimpleVector {
T data[10];
public:
T& operator[](size_t i) { return data[i]; }
};
Included in main1.cpp and main2.cpp, instantiations like SimpleVector<int> v; produce matching code in each unit, complying with ODR via identical template definitions.[1] No separate source file is needed, as the compiler handles instantiation per use.[19]
Example 3: C++17 Inline Static Variable
In C++17, static data members declared inline can be defined directly in the class within a header, allowing multiple inclusions without an out-of-class definition, as the linker merges them like inline functions. For example, in config.h:
cpp
struct Config {
static inline int maxSize = 100;
};
struct Config {
static inline int maxSize = 100;
};
When config.h is included in multiple .cpp files accessing Config::maxSize, the identical initializer ensures ODR compliance, avoiding multiple definition errors.[1] This simplifies header-only libraries by eliminating explicit definitions.
Example 4: Pre-C++11 Static Const Integral In-Class
Prior to C++11, static constant integral data members could be initialized in-class without an out-of-class definition if not odr-used (e.g., only as constant expressions in unevaluated contexts), permitting multiple identical declarations across units. In a header constants.h:
cpp
class Limits {
public:
static const int bufferSize = 1024;
};
class Limits {
public:
static const int bufferSize = 1024;
};
Included in several .cpp files, bufferSize is treated as a compile-time constant with no storage allocation if not odr-used (e.g., via sizeof or template arguments), thus no ODR violation occurs despite multiple "definitions."[1] Odr-use, like taking its address, requires an out-of-class definition in one unit.
Example 5: Module Interface in C++20
In C++20, modules allow exporting definitions from a module interface unit (MIU), where entities like inline functions or variables can be defined once and imported across translation units without duplication, respecting ODR through module reachability rules. For example, a module interface utils.ixx:
cpp
export module Utils;
export inline int add(int a, int b) {
return a + b;
}
export module Utils;
export inline int add(int a, int b) {
return a + b;
}
This is compiled into a module interface unit, then imported in file1.cpp and file2.cpp via import Utils;, both using add(1, 2). The definition is shared via the module, ensuring a single instance without token sequence checks across traditional headers, as private module fragments (PMF) can contain non-exported differing details without ODR violation.[1] This promotes modular code with stricter linkage isolation compared to headers.
Non-Compliant Cases and Side Effects
One common non-compliant case arises when a non-inline function is defined directly in a header file that is included across multiple translation units, resulting in multiple definitions that violate the ODR and typically trigger a linker error. For instance, consider a header file utils.h containing:
cpp
void process(int value) {
// Implementation here
return value * 2;
}
void process(int value) {
// Implementation here
return value * 2;
}
If utils.h is included in both main.cpp and helper.cpp, the linker will detect duplicate symbols for process, refusing to produce an executable and reporting an error such as "multiple definition of 'process'". This detectable violation ensures the issue is caught at link time, preventing deployment of faulty code.
Another violation occurs when class definitions differ subtly across translation units, leading to type mismatches and undefined behavior at runtime due to incompatible layouts, such as varying member offsets. For example, in a.cpp:
cpp
struct S { [int](/page/INT) a; };
struct S { [int](/page/INT) a; };
And in b.cpp:
cpp
[class](/page/Class) S { public: [int](/page/INT) a; };
[class](/page/Class) S { public: [int](/page/INT) a; };
Although both declare a type named S with an int member, the differing token sequences (e.g., struct vs. class, absence of access specifier) mean they are not the same entity, violating the ODR. This can cause buffer overruns or misaligned accesses when objects are passed between units, as the compiler assumes identical layouts.
Prior to C++11, odr-using a static const data member without an out-of-class definition also breaches the ODR, often manifesting as a linker error.[12] Consider:
cpp
class Config {
public:
static const int MAX_SIZE = 100;
};
int main() {
int* ptr = new int[Config::MAX_SIZE]; // ODR-use via array size
// ...
}
class Config {
public:
static const int MAX_SIZE = 100;
};
int main() {
int* ptr = new int[Config::MAX_SIZE]; // ODR-use via array size
// ...
}
Here, MAX_SIZE requires a namespace-scope definition like const int Config::MAX_SIZE; in a single source file; omitting it leads to unresolved symbols at link time if the member is odr-used (e.g., for its address or in constant expressions).[12] Post-C++11, in-class initialization suffices for integral types, but pre-C++11 codebases must provide explicit definitions to comply.[12]
Template specializations that differ between translation units represent a subtle violation, potentially causing incorrect instantiations and undefined behavior without immediate linker feedback. For example, a header declares template<typename T> void func(T) { /* generic */ }, but unit1.[cpp](/page/CPP) adds a specialization:
cpp
template<> void func(int) { /* version A */ }
template<> void func(int) { /* version A */ }
While unit2.[cpp](/page/CPP) provides:
cpp
template<> void func(int) { /* version B, differing body */ }
template<> void func(int) { /* version B, differing body */ }
The compiler may instantiate the wrong version in one unit, leading to mismatched behavior when linking, such as invoking unintended logic.[20] This undetectable case ties into broader undefined behavior from ODR violations, as detailed in related sections.
Such non-compliant cases can produce severe side effects, including runtime crashes from address mismatches (e.g., pointers to different class instances) or incorrect virtual tables causing failed dynamic dispatch. For instance, differing class layouts might corrupt vtable pointers, leading to segmentation faults during polymorphism.[21] Compiler behaviors vary: GCC's -Wodr flag detects some class violations via vtable analysis, while MSVC may overlook them unless using tools like /LTCG, potentially allowing UB to surface only at runtime.[21] These effects underscore the need for consistent definitions to avoid exploits like vptr attacks.