【问题标题】:(C++) Internal compiler error when initializing with braces and rvalues(C++) 使用大括号和右值初始化时出现内部编译器错误
【发布时间】:2019-06-07 10:43:35
【问题描述】:

我遇到了一个似乎取决于我使用的初始化语法的奇怪问题。编译器仅报告内部错误,并且仅在我使用带有右值元素的初始化列表时才报告。

首先我创建了一个类型来指定一个角度值。

数学.hpp:

// ...
template<class S = float>
struct Angle { S value = 0, cosine = cos(value), sine = sin(value); };
// ...

接下来是一个四元数(数学对象,不是很重要),它具有用于常规值和轴角形式的不同构造函数。

四元数.hpp:

// ...
template<class S = float>
struct Quaternion {
    S w, x, y, z;
    // ...
    Quaternion(S && w = 0, S && x = 0, S && y = 0, S && z = 0):
        w(std::move(w)), x(std::move(x)), y(std::move(y)), z(std::move(z)) {}
    Quaternion(S const& w, S const& x, S const& y, S const& z):
        w(w), x(x), y(y), z(z) {}
    Quaternion(Angle<S> const& t = {0}, S const& x = 0, S const& y = 0, S const& z = 0):
        w(t.cosine), x(t.sine*x), y(t.sine*y), z(t.sine*z) {}
    template<class T> Quaternion(Quaternion<T> const& q):
        w(q.w), x(q.x), y(q.y), z(q.z) {}
    template<class T> Quaternion(Quaternion<T> && q):
        w(std::move(q.w)), x(std::move(q.x)), y(std::move(q.y)), z(std::move(q.z)) {}
    virtual ~Quaternion(void) {}
};
// ...

这就是它在使用中的样子 - 初始化角度和四元数的所有方法看起来都有效,但就像我之前描述的那样,只有一种方法组合会导致此内部编译器错误。

四元数.cpp:

typedef float T;
T theta = M_PI/2;
Angle<T> a { theta }, b = { theta };
Quaternion<T> q1 = 1, q2 = {2}, q3 = {3, 4, 5, 6},
    qval1(Angle<T>{theta}, 1, 0, 0),
    // qval2 = {Angle<T>{theta}, 1, 0, 0},
    // internal compiler error: in replace_placeholders_r, at cp/tree.c:2804
    qref1(a, 1, 0, 0),
    qref2 = {a, 1, 0, 0};

我正在使用 gcc 版本 7.3.0 将其编译为 C++14。是什么导致了错误?我应该报告吗?有没有解决方法,还是我应该避免这种方法?

【问题讨论】:

  • 内部编译器错误始终是编译器错误。它不会告诉您任何有关您的代码是否正确的信息。
  • gcc 8.2.1 在 C++14 或 C++17 模式下编译,包括 qval2 赋值,没有任何问题。这可能是一个已经修复的编译器错误;因此,我看到的唯一现实的选择是更新编译器,或者通过更改代码来解决错误,直到它没有失败。
  • 顺便说一句,你有一个潜在的冲突,因为 2 个构造函数对所有参数都有默认值,这意味着如果没有指定,两个都是候选者。
  • 如果默认构造的对象很频繁,你真的应该为struct编写特定的构造函数,以避免调用sin(0)cos(0)
  • @SamVarshavchik 谢谢! (Phil1970 - 是的,那时我处于自动驾驶状态。)

标签: c++ gcc c++14 internal-compiler-error


【解决方案1】:

内部编译器错误始终是编译器中的错误。最好避开这些区域,即使它“理论上应该确实有效”。

我的经验是高级初始化方法通常是编译器中的一个薄弱环节。测试套件似乎避免了这些。 19 年前,我在 gcc 使用 C 和命名字段初始化时遇到过类似的问题,在某些情况下会产生内部编译器错误。

尝试更新的编译器版本(如 gcc 8)。

如果您需要可移植代码的解决方法:添加默认构造函数并将所有初始化代码放入构造函数中。仅对普通常量值(未计算)等琐碎的事情使用初始化。

【讨论】:

  • 便携,您的意思是规避这个问题并修复这个版本的 GCC 的编译,还是一般来说?我倾向于将大括号用于只需要复制/移动值的简单对象,并在构造函数真正起作用时使用括号 - 这就是你的意思吗?而且我会避免内部编译器错误周围的区域,但这是我发现的第一个区域,我不知道那个区域是什么。 :)
  • 便携式我的意思是如果你需要你的代码在任何编译器上编译。例如,如果您将代码传递给客户,客户可能出于某种原因希望使用 gcc 7 进行编译。然后,当您的代码与 gcc 7“兼容”时,它的可移植性更高。所以,是的,我的意思是规避这个问题。在您的示例中,您使用大括号初始化,它需要调用函数来初始化变量。这与使用常量值的初始化完全不同。我的建议是在大括号初始化中仅使用普通常量,并在(用户提供的)构造函数主体中调用 sin() 和 cos() 内容。
  • 不要误会我的意思。我认为您的代码非常健全,并且 gcc 7 已损坏。当您希望您的代码也可以在损坏的编译器上工作时,才需要解决方法。如果你把你的代码放在 github 上,那么当你以这种方式编写可移植代码时,人们会很高兴,即使这意味着规避编译器错误。
  • 很公平。我很好地避免了这种语法(并升级了 GCC),但在不知道更多的情况下,我所能做的就是在几个地方进行注释,并希望下一个遇到它的人很快就能找到评论或自述文件(或这篇文章)。我没有理由认为应该归咎于那种类型的实质,所以他们在完全不同的地方遇到它的可能性似乎很大,所以他们不会找到我的评论或认为它是相关的。至少它清楚地表明这是编译器的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-10-10
  • 1970-01-01
  • 1970-01-01
  • 2011-05-18
  • 1970-01-01
  • 1970-01-01
  • 2013-02-20
相关资源
最近更新 更多