|Reply to:||Hans-J. Boehm|
C++11 atomic operations are designed to only apply to atomic types; We cannot use an atomic operation on a plain integer. There are great reasons for that:
Nonetheless, such access is possible on the vast majority of interesting
cases, in which
have bitwise identical representations
and identical alignment constraints. For example, the Linux kernel
assumes that atomic operations on int and similar types are supported.
When we designed the C++11 atomics, I was under the misimpression that it would be possible to semi-portably apply atomic operations to data not declared to be atomic, using code such as
int x; reinterpret_cast<atomic<int>&>(x).fetch_add(1);
This would clearly fail if the representations of
int differ, or if their alignments differ. But I know
that this is not an issue on platforms I care about. And, in practice,
I can easily test for a problem by checking at compile time that sizes
and alignments match.
However this is not guaranteed to be reliable,
even on platforms on which
one might expect it to work, since it may confuse type-based alias analysis
in the compiler. A compiler may assume that an
not also accessed as an
atomic<int>. (See 3.10, [Basic.lval],
Here we address the question of whether there should be some mechanism for applying atomic operations to non-atomic data. As pointed out in the next section, we address a somewhat different set of problems from prior discussion of non-atomic operations on atomic data (c.f. the last part of N3710 or the Issaquah discussion.
There is a strong argument that without any legacy considerations, all
data that must be accessed atomically should be declared appropriately
atomic<T>. And we want to strongly encourage that
But we do have large legacy code bases,
many of which have declared “atomic” variables as e.g.
volatile int instead of
Such code typically uses various platform-dependent atomics libraries,
or uses vendor-specific extensions (such as the gcc
primitives or the Microsoft
There are great reasons for such code to switch to the C++11
primitives: Well-defined semantics, better control over memory ordering,
better portability, better expected support by future compilers.
Replacing the legacy primitives with the corresponding C++11 primitives
would be an easy gain.
But it is often difficult to update all data structure declarations and
function prototypes to reflect the fact that some data must occasionally
be accessed atomically. Such changes tend to be viral and affect
much of the code base, even pieces that don't deal with atomic operations.
A good example of that is an interpreter for a language L that itself supports atomic access, possibly on a per-access basis. To express this correctly in C++11, every memory location that might conceivably be updated atomically should be declared as an atomic object. That would require non-atomic operations on atomics to implement the remaining operations. (One could instead declare memory as a byte array or union, But I don't believe that can be made fully correct, if object initialization in L is non-atomic, as it is in C++11.) This is likely to require pervasive changes to the interpreter for L. (Not to mention requiring non-atomic accesses to atomics, which we don't yet have.)
Another good example of a legacy code base that currently uses per-access atomicity is the Linux kernel. And Linus Torvalds has in the past expressed concerns about moving to the C11 model because it requires atomically updateable objects to be identified in their declaration.
In order to solve this problem, we need two facilities: One to test
whether the platform supports conversion between
atomic<T>, and a facility to actually perform the conversion.
Depending on whether there is WG14 interest, the convertibility test
could use macros, and/or
could use a type trait to test whether an implementation
supports interpreting a
T& as an
template<class T> class convertible_to_atomic<T>
An instance has a
true value member if and only if the conversion
function below has
well-defined behavior and results in a reference to an atomic.
(Open issue: How should this behave if
T is not a valid
is_lock_free(), the result should always be
determinable at compile time.
The function to perform the conversion would be a template in C++, and possibly again a type-generic macro in C:
template<class T> atomic<T>* as_atomic(volatile T*);
the result of
as_atomic can be used to update the
reference passed to it; such updates are atomic, while accesses directly
to the argument are not. The conversion function should probably traffic
in pointers, rather than references, for C compatibility.
(Open issue: Paul McKenney points out that the treatment of
arguments requires some care and choices. There is an argument for preserving
volatility of the argument, yielding a
if the argument is
volatile. However there are also many cases
in which this is undesirable, because the original code uses
volatile as a replacement for
atomic. Two variants
may be required to handle this, or we could require programmers to address
the issue with an explicit
For most implementations
as_atomic is an identity function,
convertible_to_atomic<T> returns true for all types
T, or at least those that can be used as an argument to
atomic<>. Even lock-based implementations should be fine, so long
as an external lock table is used.
The presence of these functions would imply that if
convertible_to_atomic<T> holds for
the compiler has to view
as potential aliases. We suspect that for most implementations this is
already true, since the implementation of
T field under the covers. But this requires
Clark Nelson, Paul McKenney, and Lawrence Crowl provided helpful comments.