7
\$\begingroup\$

This is my take for constexpr big / little endian conversions.

The code supports only little and big endian; mixed endian such PDP is not supported.

#include <cstdio>
#include <cstdint>

namespace myendian_impl_{

    template<typename UINT>
    UINT byteswap(UINT const a);

    template<>
    constexpr uint16_t byteswap(uint16_t const a){
        constexpr uint8_t b[] = {
            8 * (2 - 1)
        };

        auto const x =
            (0x00ffULL & a) << b[0] |
            (0xff00ULL & a) >> b[0]
        ;

        return static_cast<uint16_t>(x);
    }

    template<>
    constexpr uint32_t byteswap(uint32_t const a){
        constexpr uint8_t b[] = {
            8 * (2 - 1),
            8 * (4 - 1)
        };

        auto const x =
            (0x000000ffULL & a) << b[1] |
            (0x0000ff00ULL & a) << b[0] |
            (0x00ff0000ULL & a) >> b[0] |
            (0xff000000ULL & a) >> b[1]
        ;

        return static_cast<uint32_t>(x);
    }

    template<>
    constexpr uint64_t byteswap(uint64_t const a){
        constexpr uint8_t b[] = {
            8 * (2 - 1),
            8 * (4 - 1),
            8 * (6 - 1),
            8 * (8 - 1)
        };

        auto const x =
            (0x00000000000000ffULL & a) << b[3] |
            (0x000000000000ff00ULL & a) << b[2] |
            (0x0000000000ff0000ULL & a) << b[1] |
            (0x00000000ff000000ULL & a) << b[0] |
            (0x000000ff00000000ULL & a) >> b[0] |
            (0x0000ff0000000000ULL & a) >> b[1] |
            (0x00ff000000000000ULL & a) >> b[2] |
            (0xff00000000000000ULL & a) >> b[3]
        ;

        return static_cast<uint64_t>(x);
    }

    // ==============================

    class isBE{
        constexpr static uint32_t u4 = 1;
        constexpr static uint8_t  u1  = (const uint8_t &) u4;
    public:
        constexpr static bool value = u1 == 0;
    };

    // ==============================

    template<bool b>
    struct be_tag{};

    // ==============================

    template<typename UINT>
    constexpr UINT htobe(UINT const a, be_tag<true>){
        return a;
    }

    template<typename UINT>
    constexpr UINT htobe(UINT const a, be_tag<false>){
        return byteswap(a);
    }

} // namespace myendian_impl_

constexpr inline uint16_t htobe16(uint16_t const a){
    using namespace  myendian_impl_;
    return htobe(a, be_tag<isBE::value>{});
}

constexpr inline uint32_t htobe32(uint32_t const a){
    using namespace  myendian_impl_;
    return htobe(a, be_tag<isBE::value>{});
}

constexpr inline uint64_t htobe64(uint64_t const a){
    using namespace  myendian_impl_;
    return htobe(a, be_tag<isBE::value>{});
}


int main(){
    printf("%16x %16x\n",   0x1122,         htobe16(0x1122)         );
    printf("%16x %16x\n",   0x11223344,     htobe32(0x11223344)     );
    printf("%16lx %16lx\n", 0x1122334455667788, htobe64(0x1122334455667788) );
}
\$\endgroup\$
  • \$\begingroup\$ Does this work on machines with PDP-endian (BACD) hardware? \$\endgroup\$ – Toby Speight Aug 16 '17 at 10:23
  • \$\begingroup\$ nope. I will add comment. \$\endgroup\$ – Nick Aug 16 '17 at 12:06
  • \$\begingroup\$ I've amended the description slightly to more clearly explain what the code is supposed to do. Please make sure my edits are correct according to your intent. \$\endgroup\$ – Edward Aug 16 '17 at 13:13
6
\$\begingroup\$

In all, this looks pretty solid to me, so I have only a few suggestions:

Eliminate spurious inline

The inline keyword is only useful if this is moved to a header file, so it's not strictly needed in the context of the code in the question.

Consider defining a string literal

Here's a small enhancement: provide the ability to define a string literal. Here's how it might be done:

constexpr uint32_t operator"" _be32(unsigned long long num) {
    return htobe32(num);
}

Usage:

printf("%16x %16x\n",   0x11223344,     0x11223344_be32     );

This makes things very tidy and easy to use. Naturally, 16- and 64-bit versions are almost identical except for return type and name.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ "inline" is for header file. \$\endgroup\$ – Nick Aug 16 '17 at 13:28
  • \$\begingroup\$ Oh yes, of course! I overlooked the obvious. Fixed my answer. \$\endgroup\$ – Edward Aug 16 '17 at 13:29
  • \$\begingroup\$ Doesn't work. gcc 9.3.0 complains error: call to non-‘constexpr’ function ‘__uint32_t __bswap_32(__uint32_t)’ \$\endgroup\$ – ilya1725 Jun 25 at 18:23
  • \$\begingroup\$ @ilya1725: There is no function __bswap_32() used here. Try using constexpr htobe32() as shown. It works perfectly here using gcc 9.3.1. \$\endgroup\$ – Edward Jun 25 at 18:31
  • \$\begingroup\$ @Edward I did that. The compiler came back with the error specified. What options do you use to compile? Mine is -O2 \$\endgroup\$ – ilya1725 Jun 25 at 19:20
1
\$\begingroup\$

The isBE check is wrong. What you're actually doing is a cast from uint32_t to uint8_t, which doesn't give you any info about whether the system uses big or little endian: in either case the result that ends up in isBE::value is 0.

The correct way of doing it would be to do a reinterpret_cast, alas that's prohibited with constexpr.

Unfortunately, a constexpr check of the system endianess doesn't seem to be currently possible.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ this is old code, I can not be 100 percent certain, but code works in production. When I cast, dont forget there is reference sign &, so it should do the trick. However never tried it on big endian. \$\endgroup\$ – Nick Sep 11 at 20:14
  • \$\begingroup\$ I will do answer showing why this works \$\endgroup\$ – Nick Sep 11 at 20:27
  • \$\begingroup\$ @Nick, if you never tried this on big endian, how can you claim it works? \$\endgroup\$ – Fabio A. Sep 14 at 17:19
1
\$\begingroup\$

CORRECT Endian detection:

With the great help of Fabio A, I was able to check several codes that claim to work.

However some of them do not work and some works only in gcc.

Seems the only working way to detect endian-nes on compile time on both gcc and clang is as follows:

constexpr static auto check__(){
    #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__

        return Endian::BIG;

    #elif   defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__

        return Endian::LITTLE;

    #else

        return Endian::UNKNOWN;

    #endif
}

https://gcc.godbolt.org/z/1oMjjb

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Sorry, but you are wrong in your statement that "It in a way do u4 to be accessed as array": what is really happening is that the compiler generates a temporary variable whose type is uint8_t and casts the uint32_t variable into it. Then a const reference to that temporary is taken, and its value is copied into the u1 variable. If your claim were true, the static_assert()'s would fail on a big endian machine, right? Well, they don't. See godbolt (ARM, big endian): gcc.godbolt.org/z/K68Yn8 \$\endgroup\$ – Fabio A. Sep 14 at 17:23
  • \$\begingroup\$ thank you very much. I knew ARM have big endian but I did not know I can access in compiler explorer. \$\endgroup\$ – Nick Sep 15 at 17:13
  • \$\begingroup\$ @FabioA. github.com/nmmmnu/HM4/blob/master/include/myendian.h updated. Thanks. \$\endgroup\$ – Nick Sep 15 at 19:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.