1

The following code compiles using gcc 11+ but fails to compile with gcc version <= 10.


#include <stdint.h>

typedef volatile struct s {
    uint32_t receiver:1;
    uint32_t transmitter:1;
    uint32_t _res:30;
} s_t;

s_t get_struct(void){
    struct s m = {0};
    return m;
}

int main() {

    // compiles using gcc 11
    // does not compile using gcc 10 and lower
    s_t io1 = get_struct();

    return 0;
}

You can try for yourself here: https://godbolt.org/z/cavYxKdKo

Can you please explain why this is?

FYI, the code compiles with earlier gcc version if the individual struct members are qualified with volatile (instead of the struct itself). I don't know why this is as I thought the semantics are the same (at least they are in C).

typedef struct s {
    volatile uint32_t receiver:1;
    volatile uint32_t transmitter:1;
    volatile uint32_t _res:30;
} s_t;

Similar questions:

Daniel K.
  • 919
  • 10
  • 17
  • IIRC `get_struct` returns an `s`, top-level qualifiers (`const` & `volatile`) being ignored. So `s_t io1 = get_struct();` tries to value-initialize a `volatile s` from a pr-value of type `s`. – YSC Dec 08 '21 at 10:50
  • IMHO You'd either need a constructor for `s` taking a `volatile s` or `const_cast` the return of `get_struct`. – YSC Dec 08 '21 at 11:04
  • 2
    Compiles with gcc 10.x if you set the Standard to C++17 - which gives you mandatory copy elision. This is probably the default in gcc 11.x. – Richard Critten Dec 08 '21 at 11:48
  • @RichardCritten, thanks, that seems to point into the right direction. Consequently, gcc 11.x with Standard C++14 (`-std=c++14`) does not compile. However, I tried `-fno-elide-constructors` with gcc 11.x but that does compile. Puzzling. Anyway, the question is still open, why does this code not compile when copy elision is off? Is the implicitly declared copy constructor at odds with the `volatile` qualifier? And why is C++ so arcane? ‍♂️ – Daniel K. Dec 08 '21 at 15:11
  • `-fno-elide-constructors` does not affect mandatory copy elision. – n. m. could be an AI Dec 13 '21 at 08:31

1 Answers1

0

Thanks to helpful comments to my question, I try to answer the question myself with the best of my knowledge. Please comment if you spot mistakes.

Whether the code compiles or not has to do with the C++ version used.

The given code

  • compiles with C++17 (use gcc 11 or gcc 10 with option -std=c++17) and
  • does not compile with C++14 (use gcc 10 or gcc 11 with option -std=c++14).

The difference between both C++ versions with regard to this issue is based on "mandatory elision of copy/move operations" starting with C++17, see here.

Using C++14, appropriate constructors need to be defined in order to construct s_t io1.

To see this more easily, in the linked example switch to "x64-64 clang 13.0.0)". This compiler provides diagnostic messages that are easier to comprehend in my opinion. "clang" compiles the code when given the option -std=c++17. Otherwise, it emits error messages:

error: no matching constructor for initialization of 's_t' (aka 'volatile s')

It also lists several implicitly defined constructors that were rejected as "not viable".

In other words, we need to provide the proper constructor manually (aka. user-defined constructor).

First, we need to take care of the initialization of struct s m = {0}; because as soon as we add any user-define constructors, there won't be an implicitly defined constructor any more. See default constructor. Further, aggregate initialization does not apply any more as soon as there are user-defined constructors added to the struct.

This will do s(int i) {} for compiling (let's omit initialization or other code from constructors).

Second, we will need to provide a constructor for argument type s_t.

If I declare a constructor s(volatile struct s l) {}

"clang" complains with

error: copy constructor must pass its first argument by reference
    s(volatile struct s  l) {  }

If I declare a copy constructor s(volatile struct s & l) { }

"clang" complains with

error: no matching constructor for initialization of 's_t' (aka 'volatile s')

However, if I declare a move constructor s(volatile struct s && l) { }

the code compiles (with clang and gcc, C++14 and C++17).

This is the resulting code to try for yourself.

Daniel K.
  • 919
  • 10
  • 17