According to C++03 Standard (23.1/3) only copy-constructible class objects can be stored in STL containers. Copy-constructible is described in 20.1.3 and requires that "&" yields address of the object.
Now suppose I have this class:
class Class {
public:
Class* operator&()
{
//do some logging
return this;
}
const Class* operator&() const
{
//do some logging
return this;
}
//whatever else - assume it doesn't violate requierements
};
Can this class objects be legally stored in STL containers?
Yes. In C++03, the CopyConstructible requirements for &
, given values t
of type T
and u
of type const T
, are:
&t
has type T*
, and gives the address of t
, and&u
has type const T*
, and gives the address of u
.Your overloaded operators have this behaviour; so, assuming the class meets the other CopyConstructible and Assignable requirements, values of this type can be stored in any C++03 container.
C++11 relaxes these requirements, requiring that types be movable or copyable only in containers or operations that specifically have such requirements, and removing the rather odd specification of what &
must do; so your class is still fine, again assuming it meets all the other requirements for the particular container and set of operations you use.
While in C++03 we only had the possibility to copy construct / copy assign objects, we get two additional, by far more powerful tools to construct objects and one additional tool to assign objects: move construction and assignment and emplace construction (based on perfect forwarding).
Thanks to that, the standard gives the value_type
of a container way more leeway in its requirements. For example, you are allowed to store unique_ptr
s, which are move-only, in a std::vector
, as long as you don't use any operations that require CopyConstructible, CopyInsertable or CopyAssignable (such as assignment of one container to another).
Can this class objects be legally stored in STL containers?
Yes, none of the requirements that may be used in a container instantiated with a certain value_type
even mention the address-of operator in any way.
§17.6.3.1 [utility.arg.requirements]
The template definitions in the C++ standard library refer to various named requirements whose details are set out in tables 17–24.
(Table 17 and 18 are Comparable requirements)
Table 19 — DefaultConstructible requirements [defaultconstructible]
Expression Post-condition
T t; object t is default-initialized
T u{}; object u is value-initialized
T() a temporary object of type T is value-initialized
T{}
Table 20 — MoveConstructible requirements [moveconstructible]
Expression Post-condition
T u = rv; u is equivalent to the value of rv before the construction
T(rv) T(rv) is equivalent to the value of rv before the construction
rv’s state is unspecified
Table 21 — CopyConstructible requirements (in addition to MoveConstructible) [copyconstructible]
Expression Post-condition
T u = v; the value of v is unchanged and is equivalent to u
T(v) the value of v is unchanged and is equivalent to T(v)
Table 22 — MoveAssignable requirements [moveassignable]
Expression Return type Return value Post-condition
t = rv T& t t is equivalent to the value of
rv before the assignment
rv’s state is unspecified.
Table 23 — CopyAssignable requirements(in addition to MoveAssignable) [copyassignable]
Expression Return type Return value Post-condition
t = v T& t t is equivalent to v, the value of
v is unchanged
Table 24 — Destructible requirements [destructible]
Expression Post-condition
u.~T() All resources owned by u are reclaimed, no exception is propagated.
Then we also got the container requirements themselves. Those are based on their allocator types:
§23.2.1 [container.requirements.general] p13
All of the containers defined in this Clause and in (21.4) except array meet the additional requirements of an allocator-aware container, as described in Table 99. Given a container type
X
having an allocator_type identical toA
and avalue_type
identical toT
and given an lvaluem
of typeA
, a pointerp
of typeT*
, an expressionv
of typeT
, and an rvaluerv
of typeT
, the following terms are defined. (IfX
is not allocator-aware, the terms below are defined as ifA
werestd::allocator<T>
.)
T
is CopyInsertable intoX
means that the following expression is well-formed:allocator_traits<A>::construct(m, p, v);
T
is MoveInsertable intoX
means that the following expression is well-formed:allocator_traits<A>::construct(m, p, rv);
T
is EmplaceConstructible intoX
fromargs
, for zero or more argumentsargs
, means that the following expression is well-formed:allocator_traits<A>::construct(m, p, args);
An excerpt from the table for sequence containers (where p
is a valid const iterator and t
is an lvalue of type T
):
a.insert(p,t)
Requires:T
shall be CopyInsertable intoX
. Forvector
anddeque
,T
shall also be CopyAssignable.
Effects: Inserts a copy of t before p.
If you never use that specific insert
variant (and other members, that require CopyInsertible), your type doesn't need to be CopyInsertable. Easy as that. Same goes for all the other members. The only requirement that must be fulfilled is the Destructible requirement (logical, isn't it?).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With