0

I'm getting following error:

error: no matching function for call to ‘ab::ab(uint64_t)’

ab looks like this:

struct ab {uint32_t a; uint32_t b;} __attribute__((packed));

Why can't I cast uint64_t to ab?

os: Linux 5.7.9-arch1-1 x86_64
compiler: g++ (GCC) 10.1.0
walid barakat
  • 455
  • 1
  • 6
  • 17
yomol777
  • 463
  • 4
  • 16

3 Answers3

3

As a side note, maybe you can skip the ab struct altogether and bit-bang your way with two uint32_t packed inside a uint64_t. Here is a simple example:

#include <stdint.h>
#include <stdio.h>
int main(int argc, char **argv)
{
    uint64_t v = 0xbadf00ddeadbeef;
    printf("value is 0x");
    printf("%x"  ,(uint32_t)  (v & 0x00000000FFFFFFFF));
    printf("%x\n",(uint32_t) ((v & 0xFFFFFFFF00000000)>>32));
    return 0;
}

OrenIshShalom
  • 5,974
  • 9
  • 37
  • 87
  • @eerorika I removed the `ab` struct and now it is valid – OrenIshShalom Jul 24 '20 at 11:42
  • `((v & 0xFFFFFFFF00000000)>>32))` can be written more simply as `(v>>32)`. The low bits just fall away. +1. – Pete Becker Jul 24 '20 at 12:49
  • @PeteBecker Nevertheless, the symmetry / pattern helps with readability. Could even add `>> 0` to the previous line. – eerorika Jul 24 '20 at 12:52
  • @eerorika -- I find the extraneous stuff distracting. I know what `>>32` does. I don't what to count `F`s to see that they do nothing. – Pete Becker Jul 24 '20 at 12:54
  • @PeteBecker May be worth noting that the mask is also redundant on the first expression since the high bits also fall away; in the cast. – eerorika Jul 24 '20 at 12:56
  • @eerorika -- true, the high bits also fall away. But that's because of the cast; it's not a property of the inner expression itself. I generally prefer having casts do as little as possible, since they tend to hide their details. – Pete Becker Jul 24 '20 at 12:58
1

Why can't I cast uint64_t to ab?

Because uint64_t is not convertible (even explicitly) to ab.

If you want a to contain the bytes in lower memory addresses, and b to contain the bytes in higher memory addresses1, then you can do:

ab a = std::bit_cast<ab>(some_uint64_t);

std::bit_cast is introduced in C++20. Prior to that, it can be implemented using std::memcpy.

1 Note that this means that the value will be interpreted differently between CPU with different byte endianness. Such form of serialisation will not be useful for network communication or portable file formats. See bitmask's answer for implementation which would be portable.

I don't want to call a function every time I want to copy data.

Then enable the optimiser of your compiler so that it expands the function calls inline.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • It's worth pointing out that the `bit_cast` can be done inside the `ab::ab(uint64_t)` constructor such that conversions are easier and uniform outside of the struct. – bitmask Jul 24 '20 at 11:28
1

You cannot assign an integral type to a struct without telling C++ what that means. So, how do you do that?

-> Trust your compiler.

If you properly define your constructor:

struct ab {
  std::uint32_t a; std::uint32_t b;
  constexpr ab(std::uint64_t i = 0) noexcept
    : a(static_cast<std::uint32_t>(i))
    , b(static_cast<std::uint32_t>(i>>32)) {
  }
} __attribute__((packed));

This very clearly defines which part of the 64-bit int go where and how. And because it is inline defined the compiler will not actually emit a function but will always inline this.

You don't have to resort to dodgy UB-ridden constructs to get performant code:

ab test(std::uint64_t x) {
  ab obj = x; // <------------------------ that's all!
  return obj;
}

ab test_ub(std::uint64_t x) {
  ab obj;
  reinterpret_cast<std::uint64_t&>(obj) = x; // BAD! don't do this!
  return obj;
}

Both these functions produce the same assembly:

test(unsigned long):
        mov     rax, rdi
        ret

The compiler realises what you are doing in that constructor when initialising a packed struct and if the underlying architecture allows it, will emit a single mov instruction.

Compilers are better at this than we are, let them do their job.

bitmask
  • 32,434
  • 14
  • 99
  • 159
  • Note that this implementation behaves differently than one would expect from reinterpretation / memcpy. This copies the least significant bytes to `a` rather than the bytes in lower memory addresses. On little endian systems this means no change in order, but on big endian systems this means that the order will be swapped. This may be desirable as long as it is the intention. – eerorika Jul 24 '20 at 12:33
  • @eerorika You are absolutely correct. I'd argue that this makes it portable, while reinterpret/memcpy gives you a machine-dependent *something*. – bitmask Jul 24 '20 at 12:43
  • Indeed. My point is simply that it is subtly different than what OP appears to be trying to do. It is certainly possible that this is what OP should have been trying to do. – eerorika Jul 24 '20 at 12:45
  • @eerorika Thinking about it, one could probably add some compile-time checks to reverse the order on big-endian machines, such that it mimics memcpy more closely. – bitmask Jul 24 '20 at 12:46
  • In my opinion, it is best to avoid those. Use memcpy/bit_cast/reinterpret (as unsigned char) if you want byte order; use shift if you want order of significance (and portable communication). No need for compile-time checks for either. – eerorika Jul 24 '20 at 12:48