I have been looking at header file of bitset standard C++ library header. I found out that overloaded operator[] operator[](size_t ndx)
(defined in class bitset
) returns a temproray object of class reference
.
reference
operator[](size_t __position)
{ return reference(*this,__position); }
This overloaded operator encapsulates the concept of a single bit. An instance of this class is a proxy for an actual bit. It can be useful in expressions like
bitset<10> b;
b[2] = true;
The reference
class has defined overloaded =
operator member function so that above example can work:
//For b[i] = __x;
reference&
operator=(bool __x)
{
if (__x)
*_M_wp |= _Base::_S_maskbit(_M_bpos);
else
*_M_wp &= ~_Base::_S_maskbit(_M_bpos);
return *this;
}
However, i am confused over this expression:
if (b[2]) {
//Do something
}
The b[2]
first returns a temporary object of class reference
and then overloaded operator (operator bool() const
) is called on that returned temporary object to convert it to bool
data type.
// For __x = b[i];
operator bool() const
{ return (*(_M_wp) & _Base::_S_maskbit(_M_bpos)) != 0; }
If temporary objects (object having automatic storage class) are created on stacks, then calling an another function (operator bool() const
) shouldn't destroys the temporary object returned by the first function call (reference
object returned by reference operator[](size_t __position)
)?
What is the lifetime of temporary objects in C and C++?
From class.temporary#4, emphasized is mine.
When an implementation introduces a temporary object of a class that has a non-trivial constructor ([class.ctor], [class.copy]), it shall ensure that a constructor is called for the temporary object. Similarly, the destructor shall be called for a temporary with a non-trivial destructor ([class.dtor]). Temporary objects are destroyed as the last step in evaluating the full-expression ([intro.execution]) that (lexically) contains the point where they were created. This is true even if that evaluation ends in throwing an exception. The value computations and side effects of destroying a temporary object are associated only with the full-expression, not with any specific subexpression.
That temporary object will be destroyed at the last step of that given expression.
In fact, C++ relies on it for this very common expression can be work correctly:
int x = 0, y = 0, z = 0, t = 0;
int a = x + y + z + t;
Because x+y
is a temporary, x+y+z
is another temporary.
The lifetime of temporary will be shortened following rules in class.temporary#5
There are three contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array with no corresponding initializer ([dcl.init]). The second context is when a copy constructor is called to copy an element of an array while the entire array is copied ([expr.prim.lambda], [class.copy]). In either case, if the constructor has one or more default arguments, the destruction of every temporary created in a default argument is sequenced before the construction of the next array element, if any.
and it will be prolonged following rule in class.temporary#6:
The third context is when a reference is bound to a temporary.116 The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:
A temporary object bound to a reference parameter in a function call ([expr.call]) persists until the completion of the full-expression containing the call.
The lifetime of a temporary bound to the returned value in a function return statement ([stmt.return]) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.
A temporary bound to a reference in a new-initializer ([expr.new]) persists until the completion of the full-expression containing the new-initializer.
The first context can be seen in this example:
struct bar {
bar() { std::cout << __func__ << '\n'; }
bar(const bar&) { std::cout << __func__ << "__\n"; }
~bar() { std::cout << __func__ << '\n'; }
};
struct foo {
foo(const bar& b = bar()) { std::cout << __func__ << '\n'; }
};
int main() {
foo f[] = {foo(), foo()};
}
Above program should output:
bar
foo
~bar
bar
foo
~bar
The second context will be added to C++17, from then this program:
struct bar {
bar() { std::cout << __func__ << '\n'; }
bar(const bar&) { std::cout << __func__ << "__\n"; }
~bar() { std::cout << __func__ << '\n'; }
};
struct foo {
foo() {}
foo(const foo&, const bar& b = bar()) { std::cout << __func__ << "__\n"; }
};
struct foox {
foo f[2];
};
int main() {
foox fx;
foox yx = fx;
}
must output:
bar
foo__
~bar
bar
foo__
~bar
For the third context, you can find the answer in here, and here