1

I was playing with a bunch of new things today and came up with this. Did I do anything wrong?

/*
  binary class manipulator

  Print values or strings as binary
    by Kimberly Hamrick
*/
#include <iostream> // for C++ I/O
#include <string>   // for C++ strings

using namespace std;

namespace hamrick {
  template <typename T>
  class binary {
  public:
    explicit binary( const T value, const char *sep = " " )
      : _value( value ), _sep( sep ) {}

    friend ostream& operator<<( ostream& os, const binary& b ) {
      return b._manip_value( os, b );
    }
  private:
    const T     _value;
    const char *_sep;

    ostream& _manip_value( ostream& os, const binary& b ) const {
      // Print each bit of the value
      // Separate bytes by b._sep
      for ( int bit = sizeof b._value * 8 - 1; bit >= 0; --bit ) {
        os<< ( ( b._value & ( 1 << bit ) ) != 0 );
        if ( bit % 8 == 0 ) os<< b._sep;
      }

      return os;
    }

    // Hide the assignment operator because of warnings
    binary<T> operator=( const binary<T>& );
  };

  template <>
  class binary<string> {
  public:
    explicit binary( const string& value, const char *sep = " " )
      : _value( value ), _sep( sep ) {}

    friend ostream& operator<<( ostream& os, const binary& b ) {
      return b._manip_string( os, b );
    }
  private:
    const string  _value;
    const char   *_sep;

    ostream& _manip_string( ostream& os, const binary& b ) const {
      // Print each bit of each character
      // Separate characters by b._sep
      for ( const char *p = b._value.c_str(); *p != '\0'; ++p ) {
        for ( int bit = 7; bit >= 0; --bit )
          os<< ( ( ( *p >> bit ) & 1 ) != 0 );
        os<< b._sep;
      }

      return os;
    }

    // Hide the assignment operator because of warnings
    binary<string> operator=( const binary<string>& );
  };
}

int main() {
  cout<< hamrick::binary<int>( 1234 ) <<endl;
  cout<< hamrick::binary<string>( "ABCD" ) <<endl;

  for ( int a = 0; a < 256; a++ )
    cout<< hamrick::binary<char>( a ) <<endl;

  return 0;
}
Comments
i dont disapprove but more info would be much more helpful regarding the issue.
3
Contributors
9
Replies
10
Views
9 Years
Discussion Span
Last Post by Hamrick
0

Because I have no idea what I'm doing. It works and does what I want but when that happens I'm always told it's wrong. ;)

0

we don't have the requirements for the program so can't tell you if its right or wrong.

>>Because I have no idea what I'm doing.
If you really coded the program you posted then you know more than what you give yourself credit for.

0

a. it is probably wiser not to hold the seperator string (_sep) as a pointer; this might get us into trouble if a temporary was passed as the second constructor arg
b. the hard coded assumption that CHAR_BITS==8 should be done away with
c. we could reuse the functionality available in std::bitset<>
d. and we can enable assignment by making the member variables non-const

#include <iostream> // for C++ I/O
#include <string>   // for C++ strings
#include <bitset>
#include <limits>
using namespace std;

namespace hamrick 
{
  enum { char_bits = numeric_limits<unsigned char>::digits };
  template <typename T>  class binary
  {
    public:
      explicit binary( const T value, const char *sep = " " )
         : _value( value ), _sep( sep ) {}

      friend ostream& operator<<( ostream& os, const binary& b )
      {
        typedef bitset< sizeof(T)*char_bits > bs ;
        return os << bs( b._value ) << b._sep ;
      }
    private:
      T  _value;
      string _sep;
  };

  template <>  class binary<string> 
  {
    public:
      explicit binary( const string& value, const char *sep = " " )
        : _value( value ), _sep( sep ) {}

    friend ostream& operator<<( ostream& os, const binary& b )
    {
      for( size_t i=0 ; i < b._value.size() ; ++i )
        os << bitset< char_bits >( b._value[i] ) << b._sep ;
      return os ;
    }
    private:
      string _value;
      string _sep;
  };
}

int main()
{
  cout<< hamrick::binary<int>( 1234 ) <<endl;
  cout<< hamrick::binary<string>( "ABCD" ) <<endl;

  for ( int a = 0; a < 256; a++ )
    cout<< hamrick::binary<char>( a ) <<endl;

  return 0;
}
0

we don't have the requirements for the program so can't tell you if its right or wrong.

I don't have any requirements. I just wrote something that sounded useful and threw in a bunch of new C++ stuff I've been reading about like templates, manipulators and working with bits. Sorry about not giving more background into the program. :(

If you really coded the program you posted then you know more than what you give yourself credit for.

I coded it, but it took all day and I had to borrow ideas from all over the place to get it to work. I think your giving me too much credit. :-/

a. it is probably wiser not to hold the seperator string (_sep) as a pointer; this might get us into trouble if a temporary was passed as the second constructor arg

But I thought because it's const that can't ever happen.

b. the hard coded assumption that CHAR_BITS==8 should be done away with

Isn't a byte always 8 bits? And a nybble is 4 bits? :D But you lost me with the enum and digits thing. Can you explain how that works?

c. we could reuse the functionality available in std::bitset<>

Now I'm completely lost. :(

d. and we can enable assignment by making the member variables non-const

I thought it was better to make everything const except the stuff that can't be. So I've been wrong the whole time about that?

0

> it is probably wiser not to hold the seperator string (_sep) as a pointer; this might get us into trouble if a temporary was passed as the second constructor arg
>> But I thought because it's const that can't ever happen.

binary<int> to_binary( int n )
{
  char does_not_exist_once_fn_returns[] = "\n" ;
  binary<int> ret( n, sep ) ;
  return ret ;
}

when the function returns, lifetime of does_not_exist_once_fn_returns is over, but the returned object will hold the pointer. also, as in this example a non-const can be converted implicitly to a const.

> the hard coded assumption that CHAR_BITS==8 should be done away with
>> Isn't a byte always 8 bits? And a nybble is 4 bits?
in common usage, yes. but not as per the C standard. to quote it:
byte: The unit of storage in the execution environment large enough to hold any member of the basic character set of the execution environment. ... A byte is composed of a contiguous sequence of bits, the number of which is implementation-defined.
-- ISO/IEC 9899:1990(E) right on page 2
and the C++ standard is compatible with the C standard for the memory model of POD types.

>> you lost me with the enum and digits thing
C and cplusplus provide library headers (<limits.h> in C, <limits> in C++) which allow us to write correct code inspite of implementation differences. for a numeric type T, std::numeric_limits<T>::radix gives the radix and std::numeric_limits<T>::digits gives the number of digits to base radix.
so, numeric_limits<unsigned char>::digits gives the number of digits (bits) in an unsigned char. if we want to be pedantic, we could assert that numeric_limits<unsigned char>::radix == 2 (, but we do not need to because of C compatibility requirements). it is also possible to use the manifest constant CHAR_BITS from <climits> in place of our enum char_bits. ah, the things we need to do because we program to real (not virtual) machines.

> we could reuse the functionality available in std::bitset<>
>> Now I'm completely lost.
std::bitset<N> represents a sequence of N bits. a bitset<> is output streamable (gives a sequence of 0/1) and can be converted to and from a string. it can also be constructed using a unsigned long. in a bitset<N>, bit 0 is the least significant bit and bit N-1 is the most significant bit; It doesn't depend on the endianness of the hardware platform. (that is one less detail we have to take care of.) another advantage we get with bitset<> is that someone trying to use the binary<T> generalisation, where T is not (or implicitly convertible to) a numeric type would get a compile time error.

> and we can enable assignment by making the member variables non-const
>> I thought it was better to make everything const except the stuff that can't be. So I've been wrong the whole time about that?
you certailnly are not wrong about that; infact that notion is something that would be your friend whenever you write code. but it would be easier (and more flexible) to treat a non-mutable binary<T> as a
const binary<T> rather than a binary<T> with const members. for example

struct person
{
  const string name ; // name cannot be changed
  string address ; // address can change
  bool  operator== (  const person&  that ) const ;
  // both const qualifiers are required for const-correctness 
};

but when in doubt, always favour adding a const qualifier (over dropping it).

0

C and cplusplus provide library headers (<limits.h> in C, <limits> in C++) which allow us to write correct code inspite of implementation differences. for a numeric type T, std::numeric_limits<T>::radix gives the radix and std::numeric_limits<T>::digits gives the number of digits to base radix.
so, numeric_limits<unsigned char>::digits gives the number of digits (bits) in an unsigned char. if we want to be pedantic, we could assert that numeric_limits<unsigned char>::radix == 2 (, but we do not need to because of C compatibility requirements). it is also possible to use the manifest constant CHAR_BITS from <climits> in place of our enum char_bits. ah, the things we need to do because we program to real (not virtual) machines.

I kind of figured what it does, but I haven't used enum yet, and I don't understand how numeric_limits works.

It doesn't depend on the endianness of the hardware platform. (that is one less detail we have to take care of.)

Endianness? What's that and how do I take care of it without bitset<>?

another advantage we get with bitset<> is that someone trying to use the binary<T> generalisation, where T is not (or implicitly convertible to) a numeric type would get a compile time error.

But it doesn't let me use _sep to divide the bytes. It just prints a long string of 1's and 0's. :(

but it would be easier (and more flexible) to treat a non-mutable binary<T> as a
const binary<T> rather than a binary<T> with const members. for example

I don't understand what the example is doing. Why is that different from what I have? :confused:

0

I kind of figured what it does, but I haven't used enum yet, and I don't understand how numeric_limits works.

an unnamed enum as in enum { char_bits = numeric_limits<unsigned char>::digits }; in normal code is equivalent to const int char_bits = numeric_limits<unsigned char>::digits ; inside the body of a class, it is an just an easy way to define a constant (the value of which is known at compile time).
for numeric limits, see: http://www.dinkumware.com/manuals/?manual=compleat&page=limits2.html
ie: "http://www.dinkumware.com/manuals/?manual=compleat&page=limits2.html"

Endianness? What's that

see: http://en.wikipedia.org/wiki/Endianness

But it doesn't let me use _sep to divide the bytes. It just prints a long string of 1's and 0's.

we can do that quite easily. this is one way:

template <typename T>  class binary
  {
    public:
      explicit binary( const T value, const char *sep = " " )
         : _value( value ), _sep( sep ) {}

      friend ostream& operator<<( ostream& os, const binary& b )
      {
        bitset< sizeof(T)*char_bits > bs( b._value ) ;
        const string& str = bs.to_string() ;
        for( size_t i=0 ; i<sizeof(T) ; ++i )
           os << str.substr( i*char_bits, char_bits ) << b._sep ;
        return os ;
      }
    private:
      T  _value;
      string _sep;
  };

I don't understand what the example is doing. Why is that different from what I have?

in the above example, writing friend ostream& operator<<( ostream& os, [B]const[/B] binary& b ) implies that as far as this function is concerned, b can not be modified; neither b._value or b._sep. in a const binary, both _value and _sep are const.

struct S
{
  const int c ; // never modifiable, even if S is modifiable
  int i ; // modifiable if S is modifiable, const otherwise
  mutable int m ; // always modifiable (never const even if S is const)
  S( int ii ) : c(ii) {}
};

void foobar( S* ps, const S* pcs )
{
  // both ps->c and pcs->c are of type const int; both are non-modifiable
 //  ps->i is non-const, pcs->i is const
 //  both ps->m and pcs->m are non-const, both are modifiable
}

you were using member variables like c in the above example.
i was using member variables like i in the above example.
(the daniweb C++ formatter does not believe that mutable is a C++ keyword)

0

I don't really understand, but I don't think I will right away. I'll read all of the stuff you gave me and try to figure things out. :)

This question has already been answered. Start a new discussion instead.
Have something to contribute to this discussion? Please be thoughtful, detailed and courteous, and be sure to adhere to our posting rules.