Can Using a Lambda in Header Files Violate the Odr

Can using a lambda in header files violate the ODR?

This boils down to whether or not a lambda's type differs across translation units. If it does, it may affect template argument deduction and potentially cause different functions to be called - in what are meant to be consistent definitions. That would violate the ODR (see below).

However, that isn't intended. In fact, this problem has already been touched on a while ago by core issue 765, which specifically names inline functions with external linkage - such as f:

7.1.2 [dcl.fct.spec] paragraph 4 specifies that local static variables and string literals appearing in the body of an inline function with
external linkage must be the same entities in every translation unit
in the program. Nothing is said, however, about whether local types
are likewise required to be the same.

Although a conforming program could always have determined this by use
of typeid, recent changes to C++ (allowing local types as template
type arguments, lambda expression closure classes
) make this question
more pressing.

Notes from the July, 2009 meeting:


The types are intended to be the same.

Now, the resolution incorporated the following wording into [dcl.fct.spec]/4:

A type defined within the body of an extern inline function is the same type in every translation unit.

(NB: MSVC isn't regarding the above wording yet, although it might in the next release).

Lambdas inside such functions' bodies are therefore safe, since the closure type's definition is indeed at block scope ([expr.prim.lambda]/3).

Hence multiple definitions of f were ever well-defined.

This resolution certainly doesn't cover all scenarios, as there are many more kinds of entities with external linkage that can make use of lambdas, function templates in particular - this should be covered by another core issue.

In the meantime, Itanium already contains appropriate rules to ensure that such lambdas' types coincide in more situations, hence Clang and GCC should already mostly behave as intended.


Standardese on why differing closure types are an ODR violation follows. Consider bullet points (6.2) and (6.4) in [basic.def.odr]/6:

There can be more than one definition of […]. Given such an entity named D defined in more than one translation unit, then each definition of D shall consist of the
same sequence of tokens; and

(6.2) - in each definition of D, corresponding names, looked up
according to [basic.lookup], shall refer to an entity defined within
the definition of D, or shall refer to the same entity, after
overload resolution ([over.match])
and after matching of partial
template specialization ([temp.over]), […]; and

(6.4) - in each definition of D, the overloaded operators referred to,
the implicit calls to conversion functions, constructors,
operator new functions and operator delete functions, shall refer to
the same function, or to a function defined within the definition of
D
; […]

What this effectively means is that any functions called in the entity's definition shall be the same in all translation units - or have been defined inside its definition, like local classes and their members. I.e. usage of a lambda per se is not problematic, but passing it to function templates clearly is, since these are defined outside the definition.

In your example with C, the closure type is defined within the class (whose scope is the smallest enclosing one). If the closure type differs in two TUs, which the standard may unintentionally imply with the uniqueness of a closure type, the constructor instantiates and calls different specializations of function's constructor template, violating (6.4) in the above quote.

Why does g++ 10.1 complain about a named lambda in a header file and others do not?

I get an error when linking.

Why?

Because you violate the One Definition Rule by defining the variable multiple times.

and others do not?

Why?

¯\_(ツ)_/¯ Language implementations are not required to diagnose ODR violations.

I use the named lambda like I would use a template function, therefore I need to write my named lambda into a header file to be able to use it anywhere in the project and I cannot somehow separate declaration from definition, right?

Right.

How can I compile my project with g++ 10.1?

Simple solution: Declare the variable inline (C++17 feature).

As simple, but has curious detail that each TU has their own instance: Declare the variable static.

Third solution: The lambda captures nothing, so you might as well define a template function instead.

Fourth solution: Instead of storing the lambda as global variable, write an inline function that returns an instance of the lambda.

C++14 Generic lambdas in header file

You could store them in a header with no problem at all. IF you have the same function with the same arguments it might cause a problem, but if you have different names or arguments, it overloads it and it has no problem.

As for consts, they could be stored in headers simply to use them later in different programs. Just as functions, you could use the constant (defined by you) whenever you need it.

As "side effects" I would say that you could indlude the header in another file and use your function without having to redeclare it.

How would use of unnamed namespaces in headers cause ODR-violations?

The reason is that if you actually use anything in the anonymous
namespace, you risk undefined behavior. For example:

namespace {
double const pi = 3.14159;
}

inline double twoPiR( double r ) { return 2.0 * pi * r; }

The rule for inline functions (and classes, and templates, and
anything else which must be defined in multiple translation
units) is that the tokens must be identical (normally the case,
unless you hit some macro), and that all symbols must bind
identically. In this case, each translation unit has a separate
instance of pi, so the pi in twoPiR binds to a different
entity in each translation unit. (There are a few exceptions,
but they all involve integral expressions.)

Of course, even without the anonymous namespace, this would be
undefined behavior here (since const means internal linkage by
default), but the basic principle holds. Any use in a header of
anything in an unnamed namespace (or any const object defined in
the header) is likely to cause undefined behavior. Whether it
is a real problem or not depends, but certainly anything which
really involves the address of pi, above, is going to cause
problems. (I say "really" here, because there are many cases
where the address or a reference is formally used, but in
practice, the inline expansion will result in the value actually
being used. And of course, the token 3.14159 is 3.14159
regardless of where it appears.)

Why does a struct declaration violate the ODR in C++?

You can't define a struct more than once in a single translation unit.

You can define it in several translation units, but then the definitions have to be the same. (Source: cppreference/ODR).

To avoid this problem, you need to have an include guard in your header. It will silently prevent the header from being included more than once in each translation unit.

Are variable templates declared in a header, an ODR violation?

Templates get an exception from the one-definition rule, [basic.def.odr]/13:

There can be more than one definition of a [...] templated entity ([temp.pre]) [...] in a program provided that each definition appears in a different translation unit and the definitions satisfy the following requirements.

There's a bunch of requirements there, but basically if you have a variable template in a header (a variable template is a kind of templated entity) and just include that header from multiple translation units, they will have the same token-for-token identical definition (because #include), so having more than one definition is fine.

This is the same reason that you don't need to declare function templates inline in headers, but do need to declare regular functions inline.


In C++17, this wording read:

There can be more than one definition of a class type, enumeration type, inline function with external linkage ([dcl.inline]), inline variable with external linkage ([dcl.inline]), class template, non-static function template, static data member of a class template, member function of a class template, or template specialization for which some template parameters are not specified ([temp.spec], [temp.class.spec]) in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements.

Note that variable template is not in that list, which was just an omission (it was very much always intended to work). This was CWG 2433, adopted in 2019, but as a defect report (DR) so I wouldn't consider it as counting as a C++20 change. This is the DR that introduced the "templated entity" bullet I cited earlier (rather than listing out several different kinds of templated entity manually, and missing one).

How can __COUNTER__ cause a ODR-violation here?

Suppose the inline function is in a header included in two different translation units, and the value of the counter happens to be at a different value in each.

Then you have two definitions of the inline function with different names for the variable. That's an ODR violation - you have to use the same sequence of tokens for every definition.

(Although in practice I'd be very surprised if it caused any problem.)

does `final` violate ODR?

Yes, this is a violation of the ODR, as applied to the definition of Foo.

[basic.def.odr] paragraph 6:

There can be more than one definition of a class type ... [or other entities frequently defined in header files] ... in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity named D defined in more than one translation unit, then

  • each definition of D shall consist of the same sequence of tokens; and

  • ... [other rules to make sure all definitions have the same meaning in their context]

So any difference at all between class type definitions after preprocessing steps gives the program undefined behavior, even changing the name of an unused function parameter, changing unsigned int to int unsigned, or so on.



Related Topics



Leave a reply



Submit