0

I've seen various incarnations of my question answered/replied too but I'm still having a hard time figuring out how to omit functions my compiler states are ambiguous.

I have a class that is designed to handle streams of data. I have overloaded = and += operators so that the stream class can consume data of other types by transposing it into the template type T.

template<typename T>
class TStream
{
private:

    typedef TBuffer<T> stream_buffer;
    stream_buffer mStream;

public:

    TStream();
    TStream(const TStream &rhs);
    TStream::~TStream();

    unsigned int Size() const;
    unsigned int ByteStreamLength() const;
    void     Clear();

  // some methods omitted for brevity

    const T&operator[](unsigned int idx);
    const T*operator[](unsigned int idx) const;

    TStream<T> &operator=(const TStream &rhs);
    TStream<T> &operator=(T const);
    TStream<T> &operator=(unsigned char);
    TStream<T> &operator=(bool);
    TStream<T> &operator=(double);

  // rest omitted for brevity
};

Doing this

TStream<unsigned char> ByteStream

causes ambiguity with

operator=(unsigned char).

I basically want operator=(unsigned char) to be omitted if T = unsigned char.

This article appears to give a way to do it: http://www.drdobbs.com/function-overloading-based-on-arbitrary/184401659

But having a hard time following it because I don't want to alter my return types.

I typically use TStream like this:

   TStream<unsigned byte> ByteStream;
   ByteStream+= int
   ByteStream+= char

... etc, where I overload the += in the same way. I deduce the size coming in and convert it to the T. I do this to avoid passing a void * and a length argument for the simple POD cases.

Eric
  • 1,697
  • 5
  • 29
  • 39
  • 1
    What is this? `TStream::~ptiStream();` – Brian Bi Feb 28 '14 at 19:06
  • @BrianBi: Ooops, a typo. I was pruning code for the post and that was the old name of the template class. I made edit to fix it. – Eric Feb 28 '14 at 19:12
  • @BrianBi: Because the generic T could be of type unsigned char and I want to be able to append to the stream, say, an int type. In which case the stream will consume sizeof(int)... the int type will be converted to sizeof(int) bytes and added to the stream. – Eric Feb 28 '14 at 19:17
  • Yeah sorry I misread your code. I deleted my comment. – Brian Bi Feb 28 '14 at 19:19
  • You may want to (a) use a `T const&` for that parameter, and (b) specify the toolchain you're building all this with. – WhozCraig Feb 28 '14 at 19:20
  • @WhozCraig (a) T const & amount to passing a pointer and indirect addressing in assembly to get the actual value. Wouldn't this be more expensive in cases where your T is a simple type like an int or uint8_t? (b) tool chain is GCC using LTIB (LTIB = freescale tool chain). The code is used in embedded project. I also build the code with Embarcadero RadXE too. – Eric Feb 28 '14 at 19:27
  • @Eric certainly so, thus why I said you *may* want to. – WhozCraig Feb 28 '14 at 19:32

1 Answers1

2

There is an easy way, and a legal way.

The easy way is to use SFINAE on the function in question. Sadly, as this results in a function template for which there is no valid specialization, this is technically an ill formed program (no diagnostic required). So avoid that.

The legal way would be to use CRTP.

template<typename D, typename T, typename U>
struct implement_operator_plus_equals {
  implement_operator_plus_equals() {
    static_assert( std::is_base_of<implement_operator_plus_equals, D>::value, "CRTP failure" );
  }
  D* self() { return static_cast<D*>(this); }
  D const* self() const { return static_cast<D const*>(this); }

  typedef D Self;

  Self& operator+=( U u ) {
    static_assert( (sizeof(U)%sizeof(T)) == 0, "Non integer number of Ts in a U" );
    T* b = reinterpret_cast<T*>(&u);
    T* e = b + sizeof(U)/sizeof(T);
    for (T* it = b; it != e; ++it) {
      self()->consume(*it);
    return *self();
  }
};
template<typename D, typename T>
struct implement_operator_plus_equals<D,T,T> {
  implement_operator_plus_equals() {
    static_assert(
      std::is_base_of<implement_operator_plus_equals, D>::value, "CRTP failure"
    );
    // Have an operator+= that cannot be called, so using ...::operator+= is legal,
    // but mostly harmless:
    class block_call {};
    void operator+=( block_call ) {}
  }
};

Then use it:

template<typename D,typename T>
struct plus_equals_helper:
  public implement_operator_plus_equals< TStream<T>, T, int >,
  public implement_operator_plus_equals< TStream<T>, T, unsigned char >,
  public implement_operator_plus_equals< TStream<T>, T, bool >,
  public implement_operator_plus_equals< TStream<T>, T, double >
{
  // move += down from parents (to fix problem OP reported):
  using implement_operator_plus_equals< TStream<T>, T, int >::operator+=;
  using implement_operator_plus_equals< TStream<T>, T, unsigned char >::operator+=;
  using implement_operator_plus_equals< TStream<T>, T, bool >::operator+=;
  using implement_operator_plus_equals< TStream<T>, T, double >::operator+=;
};

template<typename T>
class TStream:
  public plus_equals_helper< TStream<T>, T >
{
public:
  void consume( T t ) { /* code */ }
  using plus_equals_helper<TStream<T>, T>::operator+=;
  TStream const& operator+=( T t ) { consume(t); return *this; }
};

this is legal, because the specializations that do nothing are perfectly ok.

On the other hand, the rule that all template functions must have at least one valid specialization is pretty obscure, and usually nothing really bad happens if you violate it. So you could just SFINAE disable your methods using a second unused default argument.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • You've got a stray `template` in last snippet. – jrok Feb 28 '14 at 20:00
  • my cross compiler doesn't appear to support std::is_base_of – Eric Feb 28 '14 at 20:06
  • @Eric that is just a sanity check -- a `static_assert` that warns you if you do something dangerous. You could implement it yourself if you like. – Yakk - Adam Nevraumont Feb 28 '14 at 21:05
  • @jrok Darn `template` got away with that `T`. – Yakk - Adam Nevraumont Feb 28 '14 at 21:06
  • Also, a [possible workaround](http://coliru.stacked-crooked.com/a/a07322258eadf90f) for the "no valid specialization rule"? – jrok Feb 28 '14 at 21:11
  • @jrok yep, that would work. Pretty silly however. :) Then again, the "no valid specialization" rule is also relatively silly. I'm hoping C++1y concepts makes requires clauses attached to `template` methods working. – Yakk - Adam Nevraumont Feb 28 '14 at 21:21
  • @Yakk just wanted to write back and send a very big thank you. Not only do I like your solution, I really appreciate how you point out the easy way and the legal way and then go on to show the legal way with working example. Although I'm aware (not familiar) with SFINAE I don't have it in my tool-chain and really appreciate the legal roll your own way of doing it (the legal way). THANK YOU! – Eric Mar 03 '14 at 15:16
  • Update: This compiled fine under Embarcadero but GCC is giving following error in the using statement. error: no members matching 'plus_equals_helper, unsigned char>::operator+=' in 'struct plus_equals_helper, unsigned char>' I thought, perhaps, the operators needed to be virtual and tried that but no luck. Any idea why GCC has problem and how to resolve? – Eric Mar 05 '14 at 20:54
  • @eric hmm it is not pulling them from 2 steps up. Add `using` directives (one per parent type) to the helper to pull `+=` down to it. Move the implementation of `operator+=(T)` from the main class to the `` specialization, **or** put an uncallable `+=` in that specialization (so the `using` directive is legal). Dunno if g++ is right. – Yakk - Adam Nevraumont Mar 05 '14 at 21:16
  • @eric updated with the "uncallable `+=`" implementation. – Yakk - Adam Nevraumont Mar 05 '14 at 21:22