Home » C++ » Can using a lambda in header files violate the ODR?

Can using a lambda in header files violate the ODR?

Posted by: admin November 29, 2017 Leave a comment

Questions:

Can the following be written in a header file:

inline void f () { std::function<void ()> func = [] {}; }

or

class C { std::function<void ()> func = [] {}; C () {} };

I guess in each source file, the lambda’s type may be different and therefore the contained type in std::function (target_type‘s results will differ).

Is this an ODR (One Definition Rule) violation, despite looking like a common pattern and a reasonable thing to do? Does the second sample violate the ODR every time or only if at least one constructor is in a header file?

Answers:

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.

Questions:
Answers:

UPDATED

After all I agree with @Columbo answer, but want to add the practical five cents 🙂

Although the ODR violation sounds dangerous, it’s not really a serious problem in this particular case. The lambda classes created in different TUs are equivalent except their typeids. So unless you have to cope with the typeid of a header-defined lambda (or a type depending on the lambda), you are safe.

Now, when the ODR violation is reported as a bug, there is a big chance that it will be fixed in compilers that have the problem e.g. MSVC and probably some other ones which don’t follow the Itanium ABI. Note that Itanium ABI conformant compilers (e.g. gcc and clang) are already producing ODR-correct code for header-defined lambdas.