File: endian.h

package info (click to toggle)
libnop 0.0~git20200728.45dfe0f-5
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 1,452 kB
  • sloc: cpp: 13,946; ansic: 3,537; makefile: 100; python: 73
file content (177 lines) | stat: -rw-r--r-- 6,732 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/*
 * Copyright 2017 The Native Object Protocols Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef LIBNOP_INCLUDE_NOP_UTILITY_ENDIAN_H_
#define LIBNOP_INCLUDE_NOP_UTILITY_ENDIAN_H_

#include <cstdint>
#include <type_traits>
#include <utility>

//
// Little-endian and big-endian to host-endian conversion utilities. These
// utilities are portable and very efficient on modern compilers.
//
// While the template expressions used here are somewhat abstruse, they are
// based on a very simple shift-or endian-conversion technique that modern
// compilers recognize and optimze well. The additional complexity supports
// standards compliance, constexpr function requirements, and larger integral
// types that may be introduced with future architectures, while avoiding
// platform dependent APIs and unnecessary branches.
//
// The following example demonstrates this conversion technique using a 4-byte
// big-endian integral type:
//
// const std::uint8_t bytes[] = {0x33, 0x22, 0x11, 0x00};
// std::uint32_t host_endian =
//               static_cast<std::uint32_t>(bytes[0]) << 24 |
//               static_cast<std::uint32_t>(bytes[1]) << 16 |
//               static_cast<std::uint32_t>(bytes[2]) <<  8 |
//               static_cast<std::uint32_t>(bytes[3]) <<  0;
// assert(host_endian == 0x33221100);
//
// This idiom works regardless of the endianness of the host because shift
// operators are defined to operate in host-endianness. The compiler recognizes
// this idiom and produces an optimal conversion. On architectures with
// byte-swap instructions modern compilers optimize this sequence to zero or one
// instruction, depending on the host endianness.
//

namespace nop {

// Base type for endian conversions to host endianness.
template <typename T, typename Enable = void>
struct HostEndian;

// Specialization for integral types.
template <typename T>
struct HostEndian<T, std::enable_if_t<std::is_integral<T>::value>> {
  // Returns the given big-endian value in host endianness.
  static constexpr T FromBig(T value) {
    const std::size_t N = sizeof(T);
    union {
      T value;
      std::uint8_t data[N];
    } u{value};
    return FromBig(u.data, std::make_index_sequence<N>{});
  }

  // Returns the given host-endian value in big endianness.
  static constexpr T ToBig(T value) { return FromBig(value); }

  // Returns the given little-endian value in host endianness.
  static constexpr T FromLittle(T value) {
    const std::size_t N = sizeof(T);
    union {
      T value;
      std::uint8_t data[N];
    } u{value};
    return FromLittle(u.data, std::make_index_sequence<N>{});
  }

  // Returns the given host-endian value in little endianness.
  static constexpr T ToLittle(T value) { return FromLittle(value); }

 private:
  // Allow other specializations to access private members.
  template <typename, typename>
  friend struct HostEndian;

  // Utilities to shift-or the little-endian or big-endian bytes into a
  // host-endian value. These use an index sequence to build the byte indices
  // and shift values needed for the shift-or endianness conversion. The use of
  // initializer_list is only to facilitate the index parameter pack expansion,
  // in lieu of C++17 fold expressons which are not as widely supported as of
  // this writing.
  template <std::size_t N, std::size_t... Is>
  static constexpr T FromLittle(const std::uint8_t (&value)[N],
                                std::index_sequence<Is...>) {
    T out = 0;
    (void)std::initializer_list<bool>{
        (out |= static_cast<T>(value[Is]) << Is * 8, false)...};
    return out;
  }
  template <std::size_t N, std::size_t... Is>
  static constexpr T FromBig(const std::uint8_t (&value)[N],
                             std::index_sequence<Is...>) {
    T out = 0;
    (void)std::initializer_list<bool>{
        (out |= static_cast<T>(value[Is]) << (N - Is - 1) * 8, false)...};
    return out;
  }
};

// Specialization for floating point types. This type forwards the bytes over to
// the integral version to perform the actual conversion, since there is no
// conversion idiom for floating point types.
//
// Note: The endianness of floating point types is not well defined by
// standards. On most modern architectures the endianness of floating point
// types matches that of integral types, but this is by no means dependable. For
// this reason it is best to avoid using floating point types when portability
// is a concern. Support is included in this library for situations where
// communication is restricted to the same machine or to machines with a known
// set of architectures.
template <typename T>
struct HostEndian<T, std::enable_if_t<std::is_floating_point<T>::value>> {
  static constexpr T FromBig(T value) {
    const std::size_t N = sizeof(T);
    union {
      T value;
      std::uint8_t data[N];
    } input{value};
    union {
      Integral value;
      T native;
    } output{HostEndian<Integral>::FromLittle(input.data,
                                              std::make_index_sequence<N>{})};
    return output.native;
  }

  // Returns the given host-endian value in big endianness.
  static constexpr T ToBig(T value) { return FromBig(value); }

  static constexpr T FromLittle(T value) {
    const std::size_t N = sizeof(T);
    union {
      T value;
      std::uint8_t data[N];
    } input{value};
    union {
      Integral value;
      T native;
    } output{HostEndian<Integral>::FromBig(input.data,
                                           std::make_index_sequence<N>{})};
    return output.native;
  }

  // Returns the given host-endian value in little endianness.
  static constexpr T ToLittle(T value) { return FromLittle(value); }

 private:
  // Type matching utility to map from floating point to integral type of the
  // same size. The type long double is not included because there is no well
  // defined integral of the same size.
  static std::uint32_t IntegralType(float);
  static std::uint64_t IntegralType(double);

  // Integral type that matches the size of floating point type T.
  using Integral = decltype(IntegralType(std::declval<T>()));
};

}  // namespace nop

#endif  // LIBNOP_INCLUDE_NOP_UTILITY_ENDIAN_H_