Forced and easy hton/ntoh conversion Context:

When sending message across the network, the sender has to convert the message from host byte order to network byte order and the receiver has to do the the reverse -  network order to host order.

There are multiple ways of going about it. One neat way to do is to convert the message to host byte
order as soon as it is received.

Eg:

struct Msg {
   uint32_t a;
   uint16_t b;
};

run_ntoh (Msg *m)
{
    m-&gt;a = ntohl(m-&gt;a);
    m-&gt;b = ntohs(m-&gt;b);
}

process (Msg *m)
{
   run_ntoh(m);
   further_process(m);   
}


But there are cases where this will not work. Say, the message buffer is shared by multiple receivers.
We can always copy the message to local buffer and do it. But that is inefficient if the message is large.

The third option is to always access the fields of 'Msg' using ntohx function. But this is error prone.

a. The user might forget to use ntoh always  - it is not enforced.

b. Also user has to be very careful - use htonl or htons? What if the size of the varible is changed -
    nothing enforces the change of htonl instead of htons if the variable size changes from 16 to 32.

Here my idea is to enforce the use of ntoh, while making it easy for the user.

Code:

#include &lt;cstdint&gt;
#include &lt;cstddef&gt;
#include &lt;type_traits&gt;

//
// class to extract N number of bytes and form a number
// uint32_t value = ExtractValue&lt;uint32_t&gt;::get(buffer);
//
template &lt;typename T, size_t val = sizeof(T)&gt;
struct ExtractValue {
    static T get (uint8_t *p)
    {
        return (ExtractValue&lt;T, val - 1&gt;::get(p) &lt;&lt; 8) |
                static_cast&lt;T&gt;(p[val - 1]);
    }
};

template &lt;typename T&gt;
struct ExtractValue&lt;T, 0&gt; {
    static T get (uint8_t *p)
    {
        return 0;
    }
};

// Get a field of the class T, in host byte order.
// The class T is wrapped in NetworkData class. The get() function
// takes the offset to the member as an argument, gets the address
// of field and extract the field
template &lt;typename T&gt;
struct NetworkData {

    using type = T;

    NetworkData (uint8_t *p) : _p(p) {}

    template&lt;typename R, typename U = std::decay_t&lt;R&gt;,
             typename std::enable_if_t&lt;!std::is_pointer&lt;U&gt;::value&gt;* =  nullptr&gt;
    U get (uintptr_t offset)
    {
        return ExtractValue&lt;U&gt;::get(_p + offset);
    }

    template&lt;typename R, typename U = std::decay_t&lt;R&gt;,
             typename std::enable_if_t&lt;std::is_pointer&lt;U&gt;::value&gt;* = nullptr&gt;
    U get (uintptr_t offset)
    {
        return (U)(_p + offset);
    }

private:
    uint8_t *_p;
};

// Macro to easily call the NetworkData::get()
#define GetNtoh(h, f)  \
    h.get&lt;decltype(decltype(h)::type::f)&gt;(offsetof(decltype(h)::type, f))


Usage:

struct Msg {
   uint32_t a;
   uint16_t b;
};

void process (uint8_t *p)
{
    NetworkData&lt;Msg&gt; h(p);

    auto a = GetNtoh(h, a);
    auto b = GetNtoh(h, b);
}


It is always better to use something like goolge protobuf to serialize/deserialize. But that is not possible when the sender is legacy code.
Unqualified names from C headers

It appears that your &lt;cstdint&gt; happens to declare its identifiers in the global namespace as well as in std; this means that you weren't notified of the consistent misspelling of std::uint8_t, std::uint16_t and the like.

Interface

It's not clear why ExtractValue is visible at file scope (rather than being a private static method within NetworkData).

The get() methods ought to be declared const (and if you never implement set() methods, then perhaps _p should be a pointer-to-const).

Pass pointer to member rather than offsetof

The use of offsetof() and pointer arithmetic with std::uint8_t* (which should be char*, given that offsetof() works in units of char) is deeply disturbing.  One possibility is for NetworkData&lt;T&gt; to hold a private reference to the T, and accept pointer-to-member to indicate which value is desired:

#include &lt;type_traits&gt;
#include &lt;utility&gt;

// Get a field of the class T, in host byte order.
// The class T is wrapped in NetworkData class. The get() function
// takes the offset to the member as an argument, gets the address
// of field and extract the field
template &lt;typename T&gt;
class NetworkData
{
    const T&amp; data;

public:
    NetworkData (void *data)
        : data(*reinterpret_cast&lt;T*&gt;(data))
    {
    }

    template&lt;typename V&gt;
    V get(V T::*field) const
    {
#if BIG_ENDIAN
        return data.*field;
#else
        return byte_swap(data.*field);
#endif
    }

private:
    template&lt;typename V&gt;
    static
    std::enable_if_t&lt;std::is_integral_v&lt;V&gt; &amp;&amp; std::is_signed_v&lt;V&gt;, V&gt;
    byte_swap(V value)
    {
        // We manipulate using unsigned bitwise arithmetic.
        return reinterpret_cast&lt;V&gt;(byte_swap(reinterpret_cast&lt;std::make_unsigned_t&lt;V&gt;&gt;(value)));
    }

    template&lt;typename V&gt;
    static
    std::enable_if_t&lt;std::is_integral_v&lt;V&gt; &amp;&amp; std::is_unsigned_v&lt;V&gt;, V&gt;
    byte_swap(V value)
    {
        // We assume CHAR_BIT == 8 (so that sizeof gives us octets)
        static const V mask = 0xff;
        V result = 0;
        for (auto shift = 0u;  shift &lt; 8 * sizeof value;  shift += 8)
            result = (result &lt;&lt; 8) + ((value &gt;&gt; shift) &amp; mask);
        return result;
    }

    // We could add overloads here to take advantage of processor-native byte swap instructions, as available.
};


We can then use it like this:

#include &lt;cstdint&gt;
struct Msg {
   std::uint32_t a;
   std::uint16_t b;
};

void process (std::uint8_t *p)
{
    NetworkData&lt;Msg&gt; h(p);

    auto a = h.get(&amp;Msg::a);
    auto b = h.get(&amp;Msg::b);
}


This eliminates the use of preprocessor macros, and makes a slightly more natural object-access syntax.

Consider modifying the structure members

If we're allowed to re-write Msg, we could make its members be network-endian values:

#include &lt;cstdint&gt;
struct Msg {
   NetworkEndian&lt;std::uint32_t&gt; a;
   NetworkEndian&lt;std::uint16_t&gt; b;
};


We can declare NetworkEndian like this:

template&lt;typename Integer&gt;
class NetworkEndian
{
    Integer value;

public:
    NetworkEndian(Integer value)
        : value(byte_swap(value))
    {}
    // default copy, assign, destructor

    operator Integer() const
    {
        return byte_swap(value);
    }

private:
    static Integer byte_swap(Integer v)
    {
        using Unsigned = std::make_unsigned_t&lt;Integer&gt;;
        Unsigned u = reinterpret_cast&lt;Unsigned&gt;(v);
        // Assume CHAR_BIT == 8 (so that sizeof gives us octets)
        static const Unsigned mask = 0xff;
        Unsigned result = 0;
        for (auto shift = 0u;  shift &lt; 8 * sizeof u;  shift += 8)
            result = (result &lt;&lt; 8) + ((u &gt;&gt; shift) &amp; mask);
        return reinterpret_cast&lt;Integer&gt;(result);
    }
};


The conversion operators mean that we can now read the members in a completely natural manner:

void process(const Msg&amp; message)
{
    auto a = message.a;
    auto b = message.b;
}


Not only that, but by providing a converting constructor, we also get to assign values equally naturally.