dimanche 31 décembre 2017

C++ class templates: to partially specialize or overload a member function?

I started off with a simple function template for extracting the bit patterns of integral types:

template <class Type>
std::string as_binary_string( Type value ) {
    static_assert( std::is_integral<Type>::value, "Integral type required." );
    return std::bitset<sizeof( Type ) * 8>( value ).to_string();
}

The above works without issue as is. I then ported this into a class template with some other functionality of the integral type: size in bytes, the max number of bit combinations, & the number of bits this type has. I save the bit pattern into an std::string. The class template worked without issue.

Then I came up with another function for doing something similar with floating point types thanks to user iBug.

template <class T>
std::string float_as_binary_string( T value ) {
    static_assert(std::is_floating_point<T>::value, "Floating Point type required.");
    std::bitset<sizeof( T ) * CHAR_BIT> b;
    std::uint8_t buf[sizeof( T ) * CHAR_BIT];
    std::memcpy( buf, &value, sizeof( T ) );

    for ( int i = 0; i < sizeof( T ); ++i ) {
        std::uint8_t cur = buf[i];
        int offset = i * CHAR_BIT;

        for ( int bit = 0; bit < CHAR_BIT; ++bit ) {
            b[offset] = cur & 1;
            ++offset;  // Move to next bit in b
            cur >>= 1; // Move to next bit in array
        }
    }
    return b.to_string();
}


I am in the process of porting this into the same class template.

I have two options: I could partially specialize this class template or the more preferred member function overload that you can see below in this class template


template<class ArithmeticType>
class BinaryRep {
public:
    static_assert(std::is_arithmetic<ArithmeticType>::value, "Type must be an Arithmetic Type.");

    static const std::size_t sizeInBytes_ = sizeof( ArithmeticType );
    static const std::size_t standardByteWidth_ = 8;
    static const std::size_t numbits_ = sizeInBytes_ * standardByteWidth_;
private:
    typedef typename std::make_unsigned<ArithmeticType>::type unsigned_t;
    const static unsigned_t maxVal_ = -1;

    ArithmeticType arTy_;
    std::string strBitPattern_;
    //std::vector<unsigned char> bitPattern_ { 0 };

public:
    BinaryRep() : arTy_(0) {
    }

    explicit BinaryRep( const ArithmeticType& arTy ) : arTy_( arTy ) {

        processBits( arTy_ );
    }

    void setVal( const ArithmeticType& arTy ) {
        arTy_ = arTy;
        processBits( arTy_ );
    }

    const std::string& getBitPattern() const {
        return strBitPattern_;
    }

    void clearBits() {
        strBitPattern_.clear();
    }

    static void showMeta() {
        std::ostringstream ostr;
        ostr << "Max Value: " << +maxVal_ << " ";
        ostr << "Size in bytes: " << sizeInBytes_ << " ";
        ostr << "Number of bits: " << numbits_ << "\n";
        std::cout << ostr.str();
    }

     static const std::string getMeta() {
        std::ostringstream ostr;
        ostr << "Max Value: " << +maxVal_ << " ";
        ostr << "Size in bytes: " << sizeInBytes_ << " ";
        ostr << "Number of bits: " << numbits_ << "\n";
        return ostr.str(); 
    }

    friend std::ostream& operator<<( std::ostream& out, const BinaryRep& val ) {
        std::ostringstream ostring;
        ostring << "Val: " << +val.arTy_ << " ";
        ostring << "Bit Pattern: ";
        ostring << val.strBitPattern_ << std::endl;
        out << ostring.str();
        return out;
    }

private:

    template<class BasicType = ArtithmeticType>
    void processBits( BasicType bt = arTy_ ) {
        strBitPattern_ = std::bitset<sizeof( BasicType ) * 8>( bt ).to_string();
    }

    // float
    template<>
    void processBits<float>( float value  ) {
        std::bitset<sizeof( ArithmeticType ) * CHAR_BIT> b;
        std::uint8_t buf[sizeof( ArithmeticType ) * CHAR_BIT];
        std::memcpy( buf, &value, sizeof( ArithmeticType ) );

        for ( int i = 0; i < sizeof( ArithmeticType ); ++i ) {
            std::uint8_t cur = buf[i];
            int offset = i * CHAR_BIT;

            for ( int bit = 0; bit < CHAR_BIT; ++bit ) {
                b[offset] = cur & 1;
                ++offset;  // Move to next bit in b
                cur >>= 1; // Move to next bit in array
            }
        }
        strBitPattern_ = b.to_string();
    }

    // double
    template<>
    void processBits<double>( double value  ) {
        std::bitset<sizeof( ArithmeticType ) * CHAR_BIT> b;
        std::uint8_t buf[sizeof( ArithmeticType ) * CHAR_BIT];
        std::memcpy( buf, &value, sizeof( ArithmeticType ) );

        for ( int i = 0; i < sizeof( ArithmeticType ); ++i ) {
            std::uint8_t cur = buf[i];
            int offset = i * CHAR_BIT;

            for ( int bit = 0; bit < CHAR_BIT; ++bit ) {
                b[offset] = cur & 1;
                ++offset;  // Move to next bit in b
                cur >>= 1; // Move to next bit in array
            }
        }
        strBitPattern_ = b.to_string();
    }
};


However, there are 2 main issues:


  • 1st issue: The above will compile, build and run if used as such:

    int main() {
        BinaryRep<char> brc;
        brc.setVal( 5 );
        std::cout << brc << std::endl;
    
        return 0;
    }
    
    

    However if I try to use it this way:

    int main() {
        BinaryRep<float> brf;
        brf.setVal( 1.0f );
        std::cout << brf << std::endl;
    
        return 0;
    }
    
    

    This will not compile and gives this MS Visual Studio 2017 CE error and I do know why!

    1>------ Build started: Project: PracticeMath, Configuration: Debug Win32 ------
    1>PracticeMath.cpp
    1>c:\program files (x86)\microsoft visual studio\2017 community\vc\tools\msvc\14.11.25503\include\type_traits(1008): error C2338: make_signed<T>/make_unsigned<T> require that T shall be a (possibly cv-qualified) integral type or enumeration but not a bool type.
    1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.11.25503\include\type_traits(1072): note: see reference to class template instantiation 'std::_Change_sign<_Ty>' being compiled
    1>        with
    1>        [
    1>            _Ty=float
    1>        ]
    1>c:\users\skilz80\documents\visual studio 2017\projects\practicemath\practicemath\binaryrep.h(42): note: see reference to class template instantiation 'std::make_unsigned<ArithmeticType>' being compiled
    1>        with
    1>        [
    1>            ArithmeticType=float
    1>        ]
    1>c:\users\skilz80\documents\visual studio 2017\projects\practicemath\practicemath\practicemath.cpp(77): note: see reference to class template instantiation 'BinaryRep<float>' being compiled
    1>Done building project "PracticeMath.vcxproj" -- FAILED.
    ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
    
    

    When trying to instantiate this class template as either a float or double type, it is failing for these lines of code from within the class template.

    typedef typename std::make_unsigned<ArithmeticType>::type unsigned_t;
    const static unsigned_t maxVal_ = -1;
    
    

    It can not make the deduced type of float nor double to be made as unsigned.

    I need a work around for this issue; and it is starting to make me think I might need to partial specialize this instead of using preferred function overload.


  • 2nd issue: If I'm not partially specializing and able to stick with the member function overload: within the showMeta() and getMeta() functions I do need to report the correct values.

    static void showMeta() {
        std::ostringstream ostr;
        ostr << "Max Value: " << +maxVal_ << " ";
        ostr << "Size in bytes: " << sizeInBytes_ << " ";
        ostr << "Number of bits: " << numbits_ << "\n";
        std::cout << ostr.str();
    }
    
    static const std::string getMeta() {
        std::ostringstream ostr;
        ostr << "Max Value: " << +maxVal_ << " ";
        ostr << "Size in bytes: " << sizeInBytes_ << " ";
        ostr << "Number of bits: " << numbits_ << "\n";
        return ostr.str(); 
    }
    
    

    Here SizeInBytes_ & numBits_ are self explanatory. The member maxVal does not mean the max numerical value that the variable can represent, in this context it means the max number of binary bit combinational representations the variable type can support. For a simple example a char & and unsigned char that is 1 byte or 8 bits in size can support 256 uniquely different binary bit representations...


At this point I'm not sure if I should stick with trying to overload the member functions, or if I do need to partially specialize this class template and once I resolve that, its a matter of getting the right meta values for floating point types. I don't know how to resolve the issue with std::make_unsigned<Type> for floating_point types. The final piece of information that is also viably important is how would you also calculate the maximum number of combinatorial binary representations for floating point types: float & double. For floating point types the most common types supported are by the IEEE standard as they are the preferred floating point types. At this current stage the endian is not an issue.

Aucun commentaire:

Enregistrer un commentaire