Skip to content

Case 111: enumerable_thread_specific Lambda-Init Ambiguity

Field Value
Verdict ๐ŸŸข COMPATIBLE
Category Addition (Compatible)
Platforms Linux, macOS, Windows
Flags Bad practice
Detected ChangeKinds func_added
Source files browse on GitHub

Category: Subtle source break / oneTBB regression suite | Verdict: ๐ŸŸข COMPATIBLE (known gap โ€” see below)

What breaks

A second constructor overload is added โ€” enumerable_thread_specific(int_factory_t) (a function-pointer-typed factory). By itself this is a pure addition: existing call sites that pass an int still resolve to the original constructor. But consumer code patterns that previously had a single viable conversion path can now become ambiguous, particularly with brace-initialization or generic callable arguments. The risk is silent โ€” code that compiled before may compile to a different constructor against the new headers, or stop compiling at unrelated call sites that infer the wrong overload.

Why function-pointer instead of std::function? A real oneTBB-flavoured example would accept std::function<int()>, but pulling in <functional> from libstdc++ 13 trips castxml/clang (__assume__ attribute in <bits/stl_bvector.h>), which would prevent the integration test from ever running. A function-pointer typedef exhibits the same overload-ambiguity risk for the purposes of this regression fixture.

Why this is in the oneTBB regression suite

Mirrors a documented oneTBB pain point: adding lambda-/functor-accepting constructor overloads to existing handle types introduced overload ambiguity in real downstream code. The pattern is repeatable across container-like types.

How abicheck catches it (and where it doesn't)

The diff exposes:

  • FUNC_ADDED: the new std::function<int()> constructor

FUNC_ADDED on a constructor is COMPATIBLE โ€” by itself it cannot link- or ABI-break anything. The follow-on overload ambiguity that breaks downstream source compilation cannot be detected from snapshots alone: it depends on the consumer's call-site context. This is a documented known_gap.

Future work (see roadmap: case105 concept tightening) is the natural home for "constructor overload set risk-classification" because both need the castxml header-AST capture path to reason about call-site resolvability.

Code diff

v1 v2
enumerable_thread_specific(int); same โ€” plus a new overload
(no other ctors) enumerable_thread_specific(int_factory_t); (typedef int (*int_factory_t)())

How to fix (as a library maintainer)

  • Constrain the lambda-init overload with a SFINAE / concept that excludes int-convertible types. e.g.:
    template <class F,
              class = std::enable_if_t<!std::is_convertible_v<F, int>>>
    explicit enumerable_thread_specific(F&& init);
    
    This eliminates the ambiguity at the call site.
  • Or expose the lambda-init as a named factory (from_lambda(...)) rather than as an overloaded constructor.

References

  • oneTBB issue tracker โ€” overload ambiguity in enumerable_thread_specific constructor set.

Source files

See also: Examples overview ยท All COMPATIBLE cases ยท Category: Addition (Compatible).