8 Thread-Safety Properties
8.1 Thread-safety attributes [dcl.attr.scpp.thread]
This document defines, for every type, two boolean properties: thread-movable and thread-shareable.
The attribute-token
scpp::thread_movableorscpp::thread_shareablemay appear in an attribute-specifier-seq ([dcl.attr.grammar]) appertaining to:
(2.1) a parameter declaration; or
(2.2) the declaration of a class or struct.
- If
[[scpp::thread_movable]]appertains to a parameter declaration, the program is ill-formed unless:
(3.1) if the parameter’s type is an rvalue reference to
U, U is thread-movable; otherwise
(3.2) the type determined for that parameter at the point of a call is thread-movable.
- If
[[scpp::thread_shareable]]appertains to a parameter declaration, the program is ill-formed unless:
(4.1) if the parameter’s type is an rvalue reference to
U, U is thread-shareable; otherwise
(4.2) the type determined for that parameter at the point of a call is thread-shareable.
If
[[scpp::thread_movable]]appertains to the declaration of a class or struct typeT,Tis thread-movable irrespective of the result structural derivation would otherwise produce for that property.If
[[scpp::thread_shareable]]appertains to the declaration of a class or struct typeT,Tis thread-shareable irrespective of the result structural derivation would otherwise produce for that property.
[Note: in the parameter form, the attribute constrains a use of the parameter’s type at a call site; in the class/struct form, it is an explicit override on the declared type itself. If the declared type is an interface under §11, the same class-level attribute may additionally impose implementor requirements under 8.5. — end note]
8.2 Structural derivation [meta.thread.struct]
If a type’s own thread-movable or thread-shareable value is not supplied by an override on that type under 8.1 or 8.4, the property’s value is determined structurally by the rules in this subclause.
A scalar type is thread-movable and thread-shareable.
An array type is thread-movable if its element type is thread-movable, and thread-shareable if its element type is thread-shareable.
A reference type is never thread-movable.
A reference to
Tis thread-shareable if and only if:
(5.1) the referred-to type is const T; and
(5.2) T is thread-shareable.
- A pointer type is neither thread-movable nor thread-shareable.
[Note: a raw pointer type already requires an explicit
[[scpp::unsafe]] context for dereference by §5.1. This
subclause likewise assigns no stronger default thread-safety property to
a raw pointer in the absence of an explicit override on a surrounding
type. — end note]
- A class or struct type
Tis thread-movable if and only if:
(7.1) T has no non-static data member of reference type;
and
(7.2) every non-static data member of T is
thread-movable.
- A class or struct type
Tis thread-shareable if and only if:
(8.1) no non-static data member of T is declared
mutable; and
(8.2) every non-static data member of T is
thread-shareable.
[Note: a mutable member affects only thread-shareable.
It does not, by itself, prevent the containing type from being
thread-movable. — end note]
- A closure type ([expr.prim.lambda.closure]) is thread-movable if and only if:
(9.1) it has no capture by lvalue reference; and
(9.2) every by-value capture member’s type is thread-movable.
- A closure type ([expr.prim.lambda.closure]) is thread-shareable if and only if:
(10.1) it has no capture by mutable lvalue reference;
(10.2) every by-value capture member’s type is thread-shareable; and
(10.3) for every capture by const lvalue reference, the
referent type is thread-shareable.
8.3 Builtin predicates [expr.prim.scpp.thread]
The forms
scpp::is_thread_movable(T)andscpp::is_thread_shareable(T)are builtin predicates.In each such form,
Tshall name a type. The token sequence between the parentheses is parsed as a type operand, not as an expression operand of an ordinary function call.Each such form is a prvalue of type
booland may appear wherever a boolean constant-expression is permitted.scpp::is_thread_movable(T)evaluates toT’s own thread-movable value, determined by:
(4.1) an override on T under 8.4, if present;
(4.2) otherwise an override on T under 8.1(5), if
present; or
(4.3) otherwise the structural derivation result for T
under 8.2.
scpp::is_thread_shareable(T)evaluates toT’s own thread-shareable value, determined by:
(5.1) an override on T under 8.4, if present;
(5.2) otherwise an override on T under 8.1(6), if
present; or
(5.3) otherwise the structural derivation result for T
under 8.2.
[Note: the syntax is analogous to a compiler builtin trait such as
__is_trivially_copyable(T), not to an ordinary function
call taking a value argument. — end note]
8.4 Conditional override [dcl.attr.scpp.thread.if]
The attribute-token
scpp::thread_movable_ifmay appear in an attribute-specifier-seq ([dcl.attr.grammar]) appertaining to the declaration of a class or struct.[[scpp::thread_movable_if(a, b)]]takes exactly two arguments.Each of
aandbshall be a boolean constant-expression.For a non-template class or struct,
ais that type’s own thread-movable value andbis that type’s own thread-shareable value.For a class or struct template,
aandbare evaluated separately for each instantiation after substituting that instantiation’s template arguments;ais that instantiation’s own thread-movable value andbis that instantiation’s own thread-shareable value.The values established by (4) or (5) replace, for that type or instantiation, the results structural derivation would otherwise produce for both properties from the type’s fields.
This attribute is an ordinary attribute available to any user declaration of a class or struct; the thread-movable or thread-shareable value of a type is not determined by any distinguished library type name.
[Note: the following declaration gives
unique_ptr<T> the same two properties as
T, independently:
template<typename T>
struct [[scpp::thread_movable_if(
scpp::is_thread_movable(T),
scpp::is_thread_shareable(T)
)]] unique_ptr {
// ...
};The following declaration gives shared_ptr<T> both
properties only when T is both thread-movable and
thread-shareable:
template<typename T>
struct [[scpp::thread_movable_if(
scpp::is_thread_movable(T) && scpp::is_thread_shareable(T),
scpp::is_thread_movable(T) && scpp::is_thread_shareable(T)
)]] shared_ptr {
// ...
};In both examples, the attribute is used exactly as it may be on any other user-declared class template. — end note]
8.5 Interface contracts and interface-typed parameters [dcl.attr.scpp.thread.interface]
This subclause applies where an interface is a class declared with
[[scpp::interface]]under §11.2.If
[[scpp::thread_movable]]appertains to the declaration of an interfaceI, then, in addition to makingIitself thread-movable under 8.1(5), every non-interface classDwhose complete object type directly or transitively inherits fromIshall be thread-movable. IfDis not thread-movable,Dis ill-formed.If
[[scpp::thread_shareable]]appertains to the declaration of an interfaceI, then, in addition to makingIitself thread-shareable under 8.1(6), every non-interface classDwhose complete object type directly or transitively inherits fromIshall be thread-shareable. IfDis not thread-shareable,Dis ill-formed.If a non-interface class directly or transitively inherits from more than one interface carrying requirements under (2) or (3), all such requirements apply conjunctively. The class is well-formed only if it satisfies every inherited requirement.
The checks required by (2)-(4) are performed at the complete definition of the non-interface class, using that type’s own thread-movable and thread-shareable values as determined under 8.1, 8.2, and 8.4.
If a parameter of reference-to-interface type or pointer-to-interface type is annotated with
[[scpp::thread_movable]]or[[scpp::thread_shareable]], 8.1(3) and 8.1(4) apply unchanged. The actual argument type supplied at each call site shall satisfy the requested property, whether or not the interface itself declares a class-level requirement under (2) or (3).
[Note: (2)-(5) provide a declaration-time blanket contract on every implementor of an interface. Rule (6) is instead a per-use-site constraint on a particular parameter. The two mechanisms are complementary, not mutually exclusive. — end note]
class [[scpp::interface, scpp::thread_shareable]] IView {
public:
virtual ~IView() = default;
virtual void render() const = 0;
};
class GoodView : public virtual IView {
public:
~GoodView() override = default;
void render() const override {}
};
class BadView : public virtual IView {
int* raw_{};
public:
~BadView() override = default;
void render() const override {}
}; // ill-formed: violates IView's inherited thread-shareable contract
void publish([[scpp::thread_shareable]] IView& view);
GoodView good{};
publish(good); // OK← Previous: Dereference and Member Access · Table of Contents · Next: Union types and packed layout →