C++
carries_dependency attribute
programming
concurrency
multithreading

What does the carries_dependency attribute mean?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

[[carries_dependency]] is a C++ attribute intended for highly specialized lock-free code that relies on dependency ordering. It tells the compiler that a dependency chain should be preserved across a function boundary for memory-order reasoning. Most modern C++ code never needs it, and many projects should avoid it for clarity.

Background: Dependency Ordering

In atomic programming, one thread may publish data and another thread may consume a pointer or index to that data. Some architectures preserve certain data-dependent ordering without a full acquire barrier. Historically, memory_order_consume attempted to model this, but practical compiler support has been weak and complicated.

[[carries_dependency]] was introduced to help propagate dependency information through function calls.

Where the Attribute Can Appear

You may apply it to function parameters or return values in declarations to indicate dependency transfer intent.

cpp
1#include <atomic>
2
3struct Node {
4    int value;
5    Node* next;
6};
7
8std::atomic<Node*> head{nullptr};
9
10[[carries_dependency]] Node* read_head() {
11    return head.load(std::memory_order_consume);
12}
13
14int read_value([[carries_dependency]] Node* p) {
15    return p ? p->value : -1;
16}

In theory, this signals that dependency from read_head result is carried into read_value argument usage.

Why It Is Rare in Production Code

In practice, many compilers map memory_order_consume to memory_order_acquire or otherwise do not provide robust dependency-based optimization. As a result, codebases often prefer explicit acquire semantics and avoid fragile dependency assumptions.

Safer mainstream approach:

cpp
1Node* p = head.load(std::memory_order_acquire);
2if (p) {
3    int v = p->value;
4    // use v
5}

This is clearer to reviewers and tools, and behavior is more portable.

Relationship to std::kill_dependency

C++ also provides std::kill_dependency, used to intentionally break dependency chains when needed for optimization. Both features target advanced use cases and are uncommon in everyday application code.

cpp
1#include <utility>
2
3int index = 5;
4int sanitized = std::kill_dependency(index);

Most teams should treat this as expert-level tuning territory.

Guidance for Real Projects

Use [[carries_dependency]] only when all conditions are true.

  • You are writing lock-free low-level components.
  • You have measured architecture-specific performance impact.
  • Toolchain behavior is validated on your supported compilers.
  • Team reviewers are comfortable maintaining this memory-model complexity.

If any condition is missing, use acquire-release semantics for maintainability.

Testing and Verification Strategy

For low-level concurrency code, combine correctness and performance validation.

  • Stress tests with high thread counts and randomized schedules.
  • Sanitizers such as ThreadSanitizer for race detection.
  • Cross-compiler CI to detect divergent behavior.
  • Microbenchmarks tied to target architecture.

Without this discipline, dependency-order tricks are more risk than value.

Function Boundary Example and Intent

The attribute primarily exists for cases where dependency should survive helper function layers. Without annotation, compiler reasoning may drop that dependency information.

cpp
1[[carries_dependency]] int* pass_through([[carries_dependency]] int* p) {
2    return p;
3}
4
5void consume(std::atomic<int*>& ptr) {
6    int* p = pass_through(ptr.load(std::memory_order_consume));
7    if (p) {
8        int v = *p;
9        (void)v;
10    }
11}

Even with this form, many teams still choose memory_order_acquire because intent is clearer and tooling support is stronger. Treat dependency attributes as low-level optimization hints, not correctness foundations.

Portability and Maintenance Tradeoff

Concurrency bugs are expensive to diagnose, so readability often beats theoretical micro-optimizations. If you adopt dependency-oriented code, document assumptions in comments and architecture notes, and keep focused regression tests for each supported compiler toolchain.

Common Pitfalls

  • Assuming [[carries_dependency]] provides universal speedups.
  • Using it in business-logic code where readability is more important.
  • Relying on memory_order_consume behavior without compiler-specific validation.
  • Mixing dependency-based and acquire-release models inconsistently.
  • Introducing subtle ordering bugs that only appear on specific CPU architectures.

Summary

  • [[carries_dependency]] is a specialized hint for dependency-chain propagation.
  • It is related to lock-free optimization and memory_order_consume semantics.
  • Real-world compiler support and portability concerns limit practical use.
  • Most projects should prefer explicit acquire-release ordering.
  • Use dependency attributes only in carefully measured, low-level concurrency code.

Course illustration
Course illustration

All Rights Reserved.