3

Suppose for the sake of this question that I am implementing betoh16(), etc., as methods in a C++ class called OpSys. While doing so I notice that all 12 or so of these functions are mostly identical, and I don't wish to duplicate so much source.

I could opt to do the following using the preprocessor...

typedef uint16_t U16;
typedef uint32_t U32;
typedef uint64_t U64;

#define BETOH_XX(nbits)                        \
  U##nbits OpSys::betoh##nbits, U##nbits val)  \
  {                                            \
    if (CPU_IS_BIG_ENDIAN)                     \
      return val;                              \
                                               \
    return bswap##nbits(val);                  \
  }

BETOH_XX(16)
BETOH_XX(32)
BETOH_XX(64)

... but I wonder if there is some way to accomplish the same thing with templates, where my calls would look something like

U16 hval = betoh<16>(beval);

I see a few other questions on SO concerning templates with constant values, but nothing that seems to employ concatenation of that templatized value to some adjacent text.

Is this possible?

Community
  • 1
  • 1
Cognitive Hazard
  • 1,072
  • 10
  • 25
  • You could make a templated `uint_bits<##>` or something with specializations and then just template `betoh` and `bswap`. – chris Jun 22 '14 at 05:39
  • "You could make a templated `uint_bits<##>`" wouldn't that be a template typedef, aka alias declaration? I think those don't allow specialization, so I'm not sure how to, e.g., define `uint_bits<##>` such that `uint_bits<16>` will map to `uint16_t`. Or did I misunderstand you? My template-fu is weak... – Cognitive Hazard Jun 22 '14 at 06:24
  • From what I recall, you're right, but it could just be a normal `struct` with a `type` member and an easy wrapper around that. – chris Jun 22 '14 at 06:28
  • Why not just use overloaded functions? – D Drmmr Jun 22 '14 at 08:36
  • Because then I would still need to write 12 of them, as I mentioned in my question. Also, this question was about expanding my knowledge of templates more than anything else. (Though it has practical applications for me, as well.) – Cognitive Hazard Jun 22 '14 at 09:52
  • @RyanV.Bissell Yes,but the implementation of the functions can just forward to a generic (template) function. Seems a lot easier and less error prone than having to specify the size each time you call one of these functions. – D Drmmr Jun 22 '14 at 10:45
  • You are correct; Philip Lenk's answer made it obvious that I was making this more complicated than necessary. Since I started with the perception that I needed to specify the size, I never considered that a generic template was simple and effective. ... In which case, I still wouldn't bother with 12 overloads. – Cognitive Hazard Jun 22 '14 at 19:51

1 Answers1

2

I do not believe concatenating arbitrary template values to form new identifiers is possible(or should be, it would kind of break the additional safety templates provide). It is, however, (as mentioned in the comments) possible to archive something similiar for your specific example.

To get the desired syntax

U16 hval = betoh<16>(beval);

it is first of all necessary to obtain one of the fixed size integer types from its bitsize. In this video http://www.youtube.com/watch?v=MvFj8qo1iuA, Andrei Alexandrescu showed a nice way of doing so at about 19:45. Basically you simply declare a template struct with a typedef type member and specialize it only for those types available, whilst leaving it undefined for all others:

template <size_t bitsize> struct uint_bits;

template<> struct uint_bits<8>{typedef uint8_t type;};
template<> struct uint_bits<16>{typedef uint16_t type;};
template<> struct uint_bits<32>{typedef uint32_t type;};

With this in place, all you need to do is template betoh as well as bswap:

template <size_t bitsize>
typename uint_bits<bitsize>::type bswap(typename uint_bits<bitsize>::type value)
{
    typename uint_bits<bitsize>::type ret_val=0;
    for(size_t b=0;b<sizeof(value);++b)
    {
        ret_val<<=8;
        ret_val|=(value&0xff);
        value>>=8;
    }
    return ret_val;
}

template <size_t bitsize>
typename uint_bits<bitsize>::type betoh(typename uint_bits<bitsize>::type value)
{
    if(CPU_IS_BIG_ENDIAN)
        return value;

    return bswap<bitsize>(value);
}

This can be used as proposed by explicitly stating the number of bits, which is, imho, kind of verbose, complicates the implementation and restricts the functions to known types, although they could work with arbitrary unsigned integers:

template <typename T>
T bswap(T value)
{
    T ret_val=0;
    for(size_t b=0;b<sizeof(value);++b)
    {
        ret_val<<=8;
        ret_val|=(value&0xff);
        value>>=8;
    }
    return ret_val;
}

template <typename T>
T betoh(T value)
{
    if(CPU_IS_BIG_ENDIAN)
        return value;

    return bswap(value);
}

This does not require the uint_bits struct and would allow the above call to be rewritten as simply

U16 hval = betoh(beval);

I am not exactly sure any of this is practical and/or efficient so ;-).

Philipp Lenk
  • 921
  • 6
  • 14
  • Nice. I sense this is what 'chris' was alluding to, in his comments to my question above. I'll give this a try after I've slept, and report back / accept. I appreciate the effort here, I believe I will learn much from studying it. – Cognitive Hazard Jun 22 '14 at 09:55
  • Hah, and I see from your 2nd implementation that I was complicating things needlessly. :) I do note that one trade-off here seems to be that I lose the ability to use fast implementations of bswap. (Unless, I suppose, I provide specializations of bswap.) – Cognitive Hazard Jun 22 '14 at 10:07