令人惊讶的 c 风格演员表

Surprising c-style cast

我正在重构我们的代码库,其中我有以下代码(简化):

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 警告我 returning reference to local temporary object (https://godbolt.org/z/ccxbjv771)。显然 (const TVector3<T> &) *this 导致调用 TVector3(const TVector4<U>& ),但为什么呢?

直觉上,我希望 (const TVector3<T> &) 表现得像重新解释的演员表,或者如果至少演员表看起来像 (const TVector3<T>) *this(没有 &),它会有点我觉得编译器选择了构造函数调用进行转换。

另一个更简单的例子:

#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;
}

它打印 B(const A&) (https://godbolt.org/z/ocWjh1eb3),但为什么呢?我正在转换为 const B& 而不是 B.

It prints B(const A&), but why ? I am converting to const B& and not to B.

a的类型是A,不能直接绑定到const B&。需要先通过B::B(const A& )转为B;然后转换后的临时 B 绑定到 const B&。 (对 const 的左值引用可以绑定到临时对象。)

它调用构造函数,因为这是 c 风格转换的规则:

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 by const_cast;
  • d) reinterpret_cast<new_type>(expression);
  • e) reinterpret_cast followed by const_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 as static_cast followed by a const_cast, it cannot be compiled.
选择

static_cast 是因为它考虑了构造函数。 请不要使用 c-style cast,你看规则不是那么简单,它迫使你对它们提出问题。

Intuitively, I would have expected (const TVector3 &) to behave like reinterpret cast

你不会想要那样的,它会破坏严格的别名。但是,如果您删除构造函数,它 将很乐意按照 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;
}

但是a不是B吗?如果您真的希望它是 B,请明确使用 reinterpret_cast(但不要)或 bit_cast。如果可能的话,明智的做法是尝试制作一份副本。它创建一个新的临时 B 并将其绑定到 const B&。如果您将它存储到 const B& b,它会延长临时文件的生命周期,使代码安全。