44

Is there a way to have a template specialization based on a range of values instead of just one? I know the following code is not valid C++ code but it shows what I would like to do. I'm writing code for a 8-bit machine, so there is a difference in speed for using ints and chars.

template<unsigned SIZE>
class circular_buffer {
   unsigned char buffer[SIZE];
   unsigned int head; // index
   unsigned int tail; // index
};

template<unsigned SIZE <= 256>
class circular_buffer {
   unsigned char buffer[SIZE];
   unsigned char head; // index
   unsigned char tail; // index
};
Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
Mike
  • 1,760
  • 2
  • 18
  • 33

4 Answers4

50

Try std::conditional:

#include <type_traits>

template<unsigned SIZE>
class circular_buffer {

    typedef typename
        std::conditional< SIZE < 256,
                          unsigned char,
                          unsigned int
                        >::type
        index_type;

    unsigned char buffer[SIZE];
    index_type head;
    index_type tail;
};

If your compiler doesn't yet support this part of C++11, there's equivalent in boost libraries.

Then again, it's easy to roll your own (credit goes to KerrekSB):

template <bool, typename T, typename F>
struct conditional {
    typedef T type;
};

template <typename T, typename F>  // partial specialization on first argument
struct conditional<false, T, F> {
    typedef F type;
}; 
jrok
  • 54,456
  • 9
  • 109
  • 141
  • 1
    I have to say, this is useful. – chris Jun 13 '12 at 16:34
  • 7
    A trait like `std::conditional` is trivial to write yourself, so you don't really need any libraries if you don't have it: `template struct conditional { typedef T type; }; template struct conditional { typedef U type; };` – Kerrek SB Jun 13 '12 at 21:31
  • Cheers @KerrekSB, I added it to the answer. – jrok Jun 14 '12 at 10:09
35

Use an extra defaulted bool parameter:

// primary template handles false
template<unsigned SIZE, bool IsSmall = SIZE <= 256>
class circular_buffer {
   unsigned char buffer[SIZE];
   unsigned int head; // index
   unsigned int tail; // index
};

// specialization for true
template<unsigned SIZE>
class circular_buffer<SIZE, true> {
   unsigned char buffer[SIZE];
   unsigned char head; // index
   unsigned char tail; // index
};
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • I'd be happier if `IsSmall = SIZE <= 256` had parens in it somewhere – Mooing Duck Jun 13 '12 at 16:39
  • @MooingDuck like this? `IsSmall = (SIZE <= 256)` –  Jun 13 '12 at 16:41
  • 3
    @MooingDuck no need. `<` is less than by default. That's why there is the `template` disambiguator. Confusingly `>` is close template by default and you need parens for that. C++ is great, isn't it? – R. Martinho Fernandes Jun 13 '12 at 16:44
  • 2
    @R.MartinhoFernandes: If it was _needed_ I would have edited the question. I just said I'd be _happier_. You still got the +1 though. – Mooing Duck Jun 13 '12 at 16:50
7

Another possible option:

template <unsigned SIZE>
struct offset_size {
    typedef typename offset_size<SIZE - 1>::type type;
};

template <>
struct offset_size<0> {
    typedef unsigned char type;
};

template <>
struct offset_size<257> {
    typedef unsigned int type;
};

template<unsigned SIZE>
class circular_buffer {
   unsigned char buffer[SIZE];
   typename offset_size<SIZE>::type head; // index
   typename offset_size<SIZE>::type tail; // index
};

(Ideone example)

cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • 8
    You're such a sadist, it's so cruel w.r.t. the compiler. Try to compile `offset_size`, the compiler will have to instantiate billions of template classes! – valdo Jun 13 '12 at 16:39
  • Indeed, if you're not careful with this technique you will trigger `error: template instantiation depth exceeds maximum of ___` (500 by default on gcc). – cdhowie Jun 13 '12 at 16:42
0

I hate how messy dealing with types can be so I propose something a bit simpler, leveraging constexpr. This variant allows for differing behavior when a varying type is not required and addresses the need to fit within a range and not just one side of a value:

template<bool> struct If;

constexpr bool InClosedRange(std::size_t Value, std::size_t Lower, std::size_t Upper)
{
    return (Lower <= Value) && (Value <= Upper);
}

// Usage:
template<size_t Width, If<true>>
class Foo;

template<size_t Width, If<InClosedRange(Width, 1, 41)>>
class Foo { /* ... */ };

template<size_t Width, If<InClosedRange(Width, 42, 142)>>
class Foo { /* ... */ };

Inspired by: https://stackoverflow.com/a/9516959/929315

Rotsiser Mho
  • 551
  • 2
  • 5
  • 19