HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

SICP streams in C++

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
streamssicpstackoverflow

Problem

To brush up on my C++ chops, I've implemented a toy version of "SICP Streams", which behave like lists with one twist: the first element of the list is always available, the rest of the list is stored as a "promise" to produce the rest of the list. Calling rest() forces its evaluation.

Following is my implementation of the general "EStream" along with a handful of specialized subclasses. But there are (at least) two areas that need attention:

-
Both head() and rest() return immutable values. I have yet to get my head around the use of const to notify the compiler that this is the case. Guidance on the use of const (or references to good documentation) would be appreciated.

-
As written, the behavior of a stream is split between the create() method and the rest() method. I'd like to find a way to restructure things all the logic for the each stream is embodied entirely either in the constructor or the rest() method, if that's possible.

(As an aside, I've created another version of EStream that uses lambdas to defer evaluation of the rest() -- it works fine, but it would be nice to not depend on c++11 constructs. Maybe that's a false concern.)

```
#include

#define NULL_ESTREAM(T) ((EStream *)0)

// (Abstract) Base Class
template class EStream {
public:
virtual T first() = 0;
virtual EStream *rest() = 0;
};

// ================================================================
// alphabetical below here

// ================
// Return elements of s0 followed by elements of s1
template class AppendES : public EStream {
public:

static EStream create( EStream s0, EStream *s1 ) {
return (s0 == NULL_ESTREAM(T)) ? s1 : new AppendES(s0, s1);
}

T first() {
return s0_->first();
}

EStream *rest() {
return AppendES::create(s0_->rest(), s1_);
}

protected:
AppendES( EStream s0, EStream s1) : s0_(s0), s1_(s1) {}
EStream *s0_;
EStream *s1_;
};

// ================
// Generate a stream of constant values
templa

Solution

Obvois catch: make your destructor virtual! The fact you do not have explicit destructor does not imply you have no destructor at all.

C++11 is widely supported these days. It provides useful tools to boost your productivity and to make code clearer. I see no reason not to use in little personal projects with no legacy or wierd constraints.

On the code itself:

My main concern is usage of pointers. Do you really need run-time polimorphism here?

In case you don't, make create functions constructors and provide additional template parameters for underling streams, like:

template  class TakeES{
public:
  TakeES(ES s, int n) {    
    n_ = n;
    s_ = s; 
  }
  T first() { 
      return s_.first();
  }
  TakeES rest() {
    return TakeES(s_.rest(), n_ - 1);
  }
  bool is_null() {
    return s_.is_null() || n_ < 1;
  }
protected:
  ES s_;
  int n_;
};


Acutually, you can go further, and make these streams compatible with stl iterators.

On the other hand, if you do want runtime polimorphism, you still need some tweaks to be made:

-
Constructors are still better then create functions, because user can deside allocation scheme for streams.

-
Plain pointers say nothing about ownage. When function takes plain pointer it usually means it will not try to own it (save for future use). right now we can easily trigger a leak:

auto stream = CyclesES::create(IntegerES::create(42));

Take unique_ptr> to communicate you are fully responsible for a life time of consumed stream.

A note about DropES. The fact it's functions should not be called means it is not a stream, so it should not inherit from it. Make it just a function. This will save a lot of headackes from users with T's not convertible from 0, etc.

Code Snippets

template <typename T, typename ES> class TakeES{
public:
  TakeES(ES s, int n) {    
    n_ = n;
    s_ = s; 
  }
  T first() { 
      return s_.first();
  }
  TakeES rest() {
    return TakeES(s_.rest(), n_ - 1);
  }
  bool is_null() {
    return s_.is_null() || n_ < 1;
  }
protected:
  ES s_;
  int n_;
};

Context

StackExchange Code Review Q#83958, answer score: 3

Revisions (0)

No revisions yet.