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;
}
Killer_Typo commented: i dont disapprove but more info would be much more helpful regarding the issue. +5

Recommended Answers

All 9 Replies

>>Did I do anything wrong?
Don't know -- why do you ask?

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. ;)

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.

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;
}

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?

> 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).

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:

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)

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. :)

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.