0

I am trying to consolidate a number of very similar function methods from a class similar to the one shown below and I thought that the best way to efficiently implement this, would be through the use templates coupled with either template function specialization or alternatively type-traits. I am a newbie to template specialization and type-traits but I understand the basic concepts and that is why I am asking for some guidance on the details. Anyway as a starting point my class is a smart buffer class that has many similar method signatures to those listed below.

class OldSafeBuffer {
public:
    intmax_t writeAt(const intmax_t& rIndex, const uint32_t val32);
    intmax_t writeAt(const intmax_t& rIndex, const int32_t val32);
    intmax_t readFrom(const intmax_t& rIndex, uint32_t& rVal32);
    intmax_t readFrom(const intmax_t& rIndex, int32_t& rVal32);
    intmax_t writeAt(const intmax_t& rIndex, const uint16_t val16);
    intmax_t writeAt(const intmax_t& rIndex, const int16_t val16);
    intmax_t readFrom(const intmax_t& rIndex, uint16_t& rVal16);
    intmax_t readFrom(const intmax_t& rIndex, int16_t& rVal16);
    intmax_t read(uint32_t& rVal32);
    intmax_t read(int32_t& rVal32);
    intmax_t read(uint16_t& rVal16);
    intmax_t read(int16_t& rVal16);
protected:
    // Actual memory storage.
    std::unique_ptr<char[]> mBuffer;
    // Buffer length
    intmax_t mBufferLength;
    // Represents the largest byte offset referenced.
    // Can be used to retrieve written length of buffer.
    intmax_t mHighWaterMark;
    // If set, caller wanted to pack data in network-byte-order.
    bool mPackNBO;
    // Set on construction, determines whether value needs to be byte-swapped.
    bool mSwapNeeded;
    // Used for file compatibility
    intmax_t mPosition;
};

I thought that this would be a perfect candidate for conversion to use template functions as these functions are very similar and I had a lot of repeated code in each method. The difference between methods was mainly the sign and the size of the 16 or 32 bit value argument.

Anyway to consolidate the readFrom methods I put the following method together. I also did similar things for the write methods. These are shown in the compiling live example.

/**
 * Read value (signed or unsigned) from buffer at given byte offset.
 *
 * @param rIndex [in]
 * @param rVal   [out]
 *
 * @return BytesRead or -1 on error
 */
template <typename T>
inline intmax_t readFrom(const intmax_t& rIndex, T& rVal)
{
    if ((rIndex + static_cast<intmax_t>(sizeof(T))) <= mBufferLength) {
        T* pVal = (T *)&mBuffer[rIndex];
        rVal = *pVal;
        // @JC Partial Template Specialization for 16 bit entities?
        if (sizeof(rVal) > sizeof(int16_t)) {
            SWAP32(rVal);
        } else {
            SWAP16(rVal);
        }
        mPosition = rIndex + sizeof(T);
        return sizeof(rVal);
    }
    return -1;
}

As can be seen from my comment, I still need to know the size of 'T& rVal' argument in order to decide whether to do a SWAP32 or SWAP16 on the argument. This was why I thought that type_traits could come in useful rather than having to put in a runtime check to compare the size of the argument.

I think that I am on the right track but I cannot figure out how to use the type_traits to check and do certain things depending on the argument type. I thought that alternatively I could use template method specialization to do special things to 16 bit arguments, but I think that would not save much effort as I would also have to specialize on both the signed adn unsigned variants of the 16 bit argument type (assuming the non specialized version was for 32 bit value arguments). Any help figuring this out would be much appreciated.

johnco3
  • 2,401
  • 4
  • 35
  • 67

3 Answers3

1

You may use something like:

template<typename T, std::size_t N = sizeof(T)> struct Swap;

template<typename T> struct Swap<T, 1> {
    void operator() (T&) const { /* Do nothing*/ }
};

template<typename T> struct Swap<T, 2> {
    void operator() (T& val) const { SWAP16(val); }
};

template<typename T> struct Swap<T, 4> {
    void operator() (T& val) const { SWAP32(val); }
};

And then call it:

Swap<T>()(rVal);

So in context:

if (sizeof(T) > sizeof(int16_t)) {
    SWAP32(val);
} else {
    SWAP16(val);
}

can be written as

Swap<T>()(val);
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • I like the way you handle the signed and unsigned variants with the sizeof argument - nice approach. – johnco3 May 31 '14 at 03:56
  • The above code does not compile even after I integrated the template specializations into my class. This is due to the macro being called which requires access to mPackNBO and mSwapNeeded members. Also, the template specialized operator()s methods require a (T& val) argument - the val was missing. Visual Studio 2013 indicates: error C2327: 'UtlSafeBuffer::mPackNBO' : is not a type name, static, or enumerator - somehow the template specializations do not see these member variables - even when they are nested within the UtlSafeBuffer. – johnco3 May 31 '14 at 12:28
  • You may move those members as parameter of swap to solve this issue. – Jarod42 May 31 '14 at 13:26
  • missing `val` parameter added BTW. – Jarod42 May 31 '14 at 13:26
  • Thanks, that worked a treat! BTW out of curiosity, do you know of a way to use type_traits to do similar type checking? I'm going to use this solution but I am curious as to how it might be done. – johnco3 May 31 '14 at 14:24
0

You could use template specialization to do a specialized swap function like the example below:

template<typename T>
struct Swap;

template<>
struct Swap<int16_t> {
  static void swap(int16_t val) { SWAP16(val); }
};

template<>
struct Swap<int32_t> {
  static void swap(int32_t val) { SWAP32(val); }
};

Then you could call it in your code like this:

template <typename T>
inline intmax_t readFrom(const intmax_t& rIndex, T& rVal)
{
    if ((rIndex + static_cast<intmax_t>(sizeof(T))) <= mBufferLength) {
        T* pVal = (T *)&mBuffer[rIndex];
        rVal = *pVal;
        Swap<T>::swap(rVal);
        mPosition = rIndex + sizeof(T);
        return sizeof(rVal);
    }
    return -1;
}
101010
  • 41,839
  • 11
  • 94
  • 168
  • What about the unsigned variants uint32_t and uint16_t? It seems like a lot of code compared to the other answers. This is why I was thinking that some form of type traits approach would be appropriate - where the specific type, signed ness and size of argument of rVal is queried at compile time. – johnco3 May 31 '14 at 03:46
0

You may just define the swap method for your 4 types:

inline void swap_endianess(int16_t& value) { SWAP16(value); }
inline void swap_endianess(uint16_t& value) { SWAP16(value); }
inline void swap_endianess(int32_t& value) { SWAP32(value); }
inline void swap_endianess(uint32_t& value) { SWAP32(value); }

and let the template function dispatches to the correct one.

so instead of

if (sizeof(T) > sizeof(int16_t)) {
    SWAP32(val);
} else {
    SWAP16(val);
}

just call

swap_endianess(val);
Jarod42
  • 203,559
  • 14
  • 181
  • 302