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\$
4
  • 1
    \$\begingroup\$ Does this work on machines with PDP-endian (BACD) hardware? \$\endgroup\$ Commented Aug 16, 2017 at 10:23
  • \$\begingroup\$ nope. I will add comment. \$\endgroup\$
    – Nick
    Commented Aug 16, 2017 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
    Commented Aug 16, 2017 at 13:13
  • \$\begingroup\$ something i'm doing locally is, instead of using b[0] b[1] b[2] b[3], i'm using 0x08 0x18 0x28 0x38, which have the same values and preserve the byte offset numbers, but are much more concise \$\endgroup\$
    – fuzzyTew
    Commented May 25, 2021 at 11:38

3 Answers 3

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.

\$\endgroup\$
10
  • \$\begingroup\$ "inline" is for header file. \$\endgroup\$
    – Nick
    Commented Aug 16, 2017 at 13:28
  • \$\begingroup\$ Oh yes, of course! I overlooked the obvious. Fixed my answer. \$\endgroup\$
    – Edward
    Commented Aug 16, 2017 at 13:29
  • 1
    \$\begingroup\$ Doesn't work. gcc 9.3.0 complains error: call to non-‘constexpr’ function ‘__uint32_t __bswap_32(__uint32_t)’ \$\endgroup\$
    – ilya1725
    Commented Jun 25, 2020 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
    Commented Jun 25, 2020 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
    Commented Jun 25, 2020 at 19:20
2
\$\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

\$\endgroup\$
4
  • \$\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.
    Commented Sep 14, 2020 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
    Commented Sep 15, 2020 at 17:13
  • \$\begingroup\$ @FabioA. github.com/nmmmnu/HM4/blob/master/include/myendian.h updated. Thanks. \$\endgroup\$
    – Nick
    Commented Sep 15, 2020 at 19:23
  • \$\begingroup\$ godbolt is useless for compile-time checks as it apparently runs on a little-endian system. Most compile-time checks will produce le, use <bit>. \$\endgroup\$ Commented Aug 10, 2022 at 12:29
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.

\$\endgroup\$
4
  • \$\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
    Commented Sep 11, 2020 at 20:14
  • \$\begingroup\$ I will do answer showing why this works \$\endgroup\$
    – Nick
    Commented Sep 11, 2020 at 20:27
  • \$\begingroup\$ @Nick, if you never tried this on big endian, how can you claim it works? \$\endgroup\$
    – Fabio A.
    Commented Sep 14, 2020 at 17:19
  • \$\begingroup\$ I did check on BE machine. and it DOES NOT work. This is why I posted "CORRECT Endian detection" answer. \$\endgroup\$
    – Nick
    Commented Aug 23, 2022 at 12:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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