4

Consider the following code:

#include <cstdint>

struct S {
  uint32_t f1;
} __attribute__((packed)); // removing this attribute makes things work.

template <class T> void func(const T &x) {}
template <class T> void func(T &&x) {} // commenting out this line makes it "work"

int main() {
    S s;
    func(s.f1); // Error here in GCC, but not clang
}

GCC gives the following error:

<source>: In function 'int main()':
<source>:16:12: error: cannot bind packed field 's.S::f1' to 'unsigned int&'
   16 |     func(s.f1);
      |          ~~^~

It appears that GCC is choosing to not allow universal references to members of a packed struct, presumably due to alignment concerns. However, clang compiles the code just fine.

Adding to my confusion is that if I remove the (T &&x) overload, it works "just fine" if only the (const T &) overload exists. I would expect that if it can't use the universal-ref overload, then it would just fall back on const-ref version... but it doesn't.

Is clang incorrect here? Is GCC? Is it just undefined behavior and therefore they are both "right"?

Evan Teran
  • 87,561
  • 32
  • 179
  • 238
  • https://gcc.gnu.org/bugzilla/show_bug.cgi?id=36566 See also: https://stackoverflow.com/questions/27491432/why-cant-i-return-a-reference-to-a-packed-field – Brandon Jun 03 '19 at 13:44
  • 2
    `__attribute__((packed))` is not covered by the standard. In practice the issues are to do with the ABI – M.M Jun 03 '19 at 14:02
  • Is there any clever ways to detect if a structure is packed so I can SFINAE away the universal reference version only for packed types? – Evan Teran Jun 03 '19 at 14:24

1 Answers1

5

The func(const T &x) is allowed because GCC will create a temporary to the packed member.

When adding a forwarding reference overload, the function call will resolve to a function that looks like func(uint32_t&). Since it's a mutable lvalue reference, no temporary can be created and the overload resolution fails, since there is no better match.

You can make it work by forcing const allowing the compiler to create a temporary again:

func(std::as_const(s).f1);
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141