2

I encountered a strange issue that appears to depend on the initialization syntax I use. The compiler only reports an internal error, and only when I use an initializer list with rvalue elements.

First I made a type to designate a value as an angle.

math.hpp:

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

Next, a quaternion (math object, not really important) with different constructors for regular values and axis-angle form.

quaternion.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) {}
};
// ...

This is what it looks like in use - all of the methods of initializing both angles and quaternions look valid, but like I described earlier, only one combination of methods causes this internal compiler error.

quaternion.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};

I am compiling this as C++14 with gcc version 7.3.0. What is causing the error? Should I report it? Is there a workaround, or should I just avoid that method?

John P
  • 1,463
  • 3
  • 19
  • 39
  • 3
    An internal compiler error is always a compiler bug. It doesn’t tell you anything about whether your code is. – Pete Becker Jan 12 '19 at 23:06
  • 2
    gcc 8.2.1 compiles, including the `qval2` assignment, without any issues, in C++14 or C++17 mode. This was a likely compiler bug that's already been fixed; as such the only realistic option I see is to update the compiler, or work around the bug by changing the code until it does not fail. – Sam Varshavchik Jan 12 '19 at 23:17
  • 2
    By the way, you have a potential conflict as 2 constructor have defaults for all parameters which means that if nothing is specified, both are candidates. – Phil1970 Jan 13 '19 at 00:50
  • 2
    If default constructed objects are frequent, you should really write specific constructors for both `struct` to avoid calling `sin(0)` and `cos(0)`. – Phil1970 Jan 13 '19 at 00:54
  • @SamVarshavchik Thanks! (Phil1970 - Yep, I was on autopilot by that point.) – John P Jan 13 '19 at 01:09

1 Answers1

1

Internal compiler errors are always errors in the compiler. It is best to avoid the areas around these, even if it "should really work in theory".

My experience is that advanced initialization methods are usually a weak field in compilers. Test suites seem to avoid these. I had similar problems 19 years ago with gcc using C and named field initialization, producing internal compiler errors for certain cases.

Try a newer compiler version (like gcc 8).

Workaround if you need your code to be portable: Add a default constructor and put all initialization code into the constructor. Use initialization only for trivial things like plain constant values (not calculated).

Johannes Overmann
  • 4,914
  • 22
  • 38
  • By portable, do you mean to circumvent this issue and fix compilation for this version of GCC, or in general? I tend to use braces for simple objects that just need values copied/moved, and parentheses when the constructor does real work - is that what you mean? And I would avoid the areas around internal compiler errors, but it's the first I've found and I don't know what that area is. :) – John P Jan 13 '19 at 01:16
  • By portable I mean if you need your code to compile on any compiler. For example if you pass your code to a customer, the customer may want to compile with gcc 7 for some reason. Then your code is more portable when it is "compatible" with gcc 7. So yes, I mean to circumvent the issue. In your example you use brace initialization which needs to call functions to initialize variables. This is quite different from initialization with constant values. My suggestion is to use only plain constants in brace initialization and call the sin() and cos() stuff in the (user provided) constructor body. – Johannes Overmann Jan 13 '19 at 23:51
  • 1
    Don't get me wrong. I think your code is perfectly sane and that gcc 7 is broken. The workaround is just needed when you want your code to work also on broken compilers. If you put your code on github you will make people happy when you write portable code this way, even if this means circumventing compiler errors. – Johannes Overmann Jan 13 '19 at 23:55
  • Fair enough. I'm fine avoiding that syntax (and upgrading GCC) but without knowing more all I can do is annotate in a couple of places and hope the next person to run into it finds the comment or readme (or this post) before too long. I have no reason to assume the substance of that type is to blame, so the odds seem pretty good that they'd encounter it somewhere completely different, so they wouldn't find my comment or think it's related. At least it makes it clear that it's an issue with the compiler. – John P Jan 14 '19 at 01:51