I am refactoring our code-base, where I have the following code (simplified):
template <typename T>
class TVector3;
template <typename T>
class TVector4;
template <typename T>
struct TVector4
{
TVector3<T>& V3() { return (TVector3<T> &) *this; }
const TVector3<T>& V3() const { return (const TVector3<T> &) *this; }
};
template <typename T>
struct TVector3
{
template <typename U>
constexpr TVector3(const TVector4<U>& v) noexcept { }
};
typedef TVector3<float> Vec3f;
typedef TVector4<float> Vec4f;
struct RGBA
{
Vec4f rgba;
operator Vec3f() const { return rgba.V3(); }
};
clang warns me about returning reference to local temporary object
(https://godbolt.org/z/ccxbjv771). Apparently (const TVector3<T> &) *this
results in calling TVector3(const TVector4<U>& )
, but why ?
Intuitively, I would have expected (const TVector3<T> &)
to behave like reinterpret cast, or if at least the cast would have looked like (const TVector3<T>) *this
(without &
) it would kind of make sense to me that the compiler picked that constructor call for conversion.
Another simpler example:
#include <iostream>
struct A { };
struct B
{
B(const A& ) { std::cout << "B(const A&)" << std::endl; }
};
int main()
{
A a;
(const B&) a;
return 0;
}
It prints B(const A&)
(https://godbolt.org/z/ocWjh1eb3), but why ? I am converting to const B&
and not to B
.
It calls the constructor because such are the rules of c-style cast:
cppreference: When the C-style cast expression is encountered, the compiler attempts to interpret it as the following cast expressions, in this order:
- a)
const_cast<new_type>(expression);
- b)
static_cast<new_type>(expression)
, with extensions: pointer or reference to a derived class is additionally allowed to be cast to pointer or reference to unambiguous base class (and vice versa) even if the base class is inaccessible (that is, this cast ignores the private inheritance specifier). Same applies to casting pointer to member to pointer to member of unambiguous non-virtual base;- c)
static_cast
(with extensions) followed byconst_cast
;- d)
reinterpret_cast<new_type>(expression);
- e)
reinterpret_cast
followed byconst_cast
. The first choice that satisfies the requirements of the respective cast operator is selected, even if it cannot be compiled (see example). If the cast can be interpreted in more than one way asstatic_cast
followed by aconst_cast
, it cannot be compiled.
static_cast
is chosen because it considers the constructors. Please do not use c-style cast, you see the rules are not so easy and it forced you to make a question about them.
Intuitively, I would have expected (const TVector3 &) to behave like reinterpret cast
You wouldn't want that, it would break strict aliasing. But if you remove the constructor, it will happily do that as per d).
#include <iostream>
struct A { };
struct B
{
B(const A& ) { std::cout << "B(const A&)" << std::endl; }
};
struct C
{
};
int main()
{
A a;
const B& temp1 = (B&) a;// Ctor, copy, prolonged-life good.
const B& temp2 = (const B&) a;// Ctor, copy, prolonged-life good.
B& dangling_temp = (B&) a;// Ctor, copy, no prolongment->dangling ref, BAD.
(const C&) a;// REINTERPET_CAST
//(const C) a;// Compiler error, good.
(const C*) &a;// REINTERPET_CAST
return 0;
}
But a
is not a B
? If you really really want it to be B
, use reinterpret_cast
(but don't) or bit_cast
explicitly. The sane thing is to attempt to make a copy if possible. It creates a new temporary B
and binds it to const B&
. Had you stored it into const B& b
, it would prolong the temporary's lifetime, making the code safe.
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