I am trying to write a program that can take a list of a specific type for example a double or float and convert it into bytes and write it to a file that can be converted back into that original list. I came up with the following structs and functions. It works for a float list, however, it does not if it is a list of doubles and I am trying to understand why.
template<typename T>
struct writer{
static constexpr size_t Size = sizeof(T);
//the size to determing if it needs to be converted to uint16_t, uint32_t, etc...
static constexpr bool U = std::conditional<std::is_unsigned_v<T>, std::true_type,
typename std::conditional<std::is_same_v<T, float>, std::true_type,
typename std::conditional<std::is_same_v<T, double>, std::true_type, std::false_type>::type >::type >::type::value;
//this is used to determine if the storing_num variable needs to be stored as unsigned or not
using value_t = std::conditional_t<sizeof(T) == 1,
std::conditional_t<U, uint8_t, int8_t>,
std::conditional_t<sizeof(T) == 2,
std::conditional_t<U, uint16_t, int16_t>,
std::conditional_t<sizeof(T) == 4,
std::conditional_t<U, uint32_t, int32_t>, //by default the only options are 1, 2, 4, and 8
std::conditional_t<U, uint64_t, int64_t> > > >;
//the value that will either be entered or bit_casted to (shown in convert_num function)
value_t storing_num;
using my_byte = std::conditional_t<U == true, uint8_t, int8_t>;
std::array<my_byte, Size> _arr;
bool convert_num(T inp){
static_assert(sizeof(T) == sizeof(value_t), "T and value_t need to be the same size");
if constexpr (!std::is_same_v<T, value_t>){
storing_num = std::bit_cast<value_t>(inp);
}else{
storing_num = inp;
}
auto begin = _arr.begin();
for(int32_t i = _arr.size() - 1; i >= 0; --i, ++begin){
*begin = ((storing_num >> (i << 3)) & 0xFF);
}
return true;
}
bool write(std::ostream& outfile){
auto begin = _arr.cbegin();
auto end = _arr.cend();
for(;begin != end; ++begin)
outfile << (char)(*begin);
return true;
}
};
The following can be used to write a float or uint32_t to a text file successfully. The following can be used to read one of the numbers back in:
template<typename T>
struct reader{
static constexpr size_t Size = sizeof(T);
static constexpr bool U = std::conditional<std::is_unsigned_v<T>, std::true_type,
typename std::conditional<std::is_same_v<T, float>, std::true_type,
typename std::conditional<std::is_same_v<T, double>, std::true_type, std::false_type>::type >::type >::type::value;
using value_t = std::conditional_t<sizeof(T) == 1,
std::conditional_t<U, uint8_t, int8_t>,
std::conditional_t<sizeof(T) == 2,
std::conditional_t<U, uint16_t, int16_t>,
std::conditional_t<sizeof(T) == 4,
std::conditional_t<U, uint32_t, int32_t>, //by default the only options are 1, 2, 4, and 8
std::conditional_t<U, uint64_t, int64_t> > > >;
value_t outp;
std::array<int8_t, Size> _arr;
bool add_nums(std::ifstream& in){
static_assert(sizeof(T) == sizeof(value_t), "T and value_t need to be the same size");
_arr[0] = in.get();
if(_arr[0] == -1)
return false;
for(uint32_t i = 1; i < _arr.size(); ++i){
_arr[i] = in.get();
}
return true;
}
bool convert(){
if(std::any_of(_arr.cbegin(), _arr.cend(), [](int v){return v == -1;}))
return false;
outp = 0;
if(U){
auto begin = _arr.cbegin();
for(int32_t i = _arr.size()-1; i >= 0; i--, ++begin){
outp += ((uint8_t)(*begin) << (i * 8));
}
return true;
}
auto begin = _arr.cbegin();
for(int32_t i = _arr.size() - 1; i >= 0; --i, ++begin)
outp += ((*begin) << (i << 3));
return true;
}
};
Then I use the following functions to iterate over a text file and read/write to said file:
template<typename T>
void read_list(T* begin, const char* filename){
reader<T> my_reader;
std::ifstream in(filename);
if(in.is_open()){
while(in.good()){
if(!my_reader.add_nums(in))
break;
if(!my_reader.convert()){
std::cerr << "error reading, got -1 from num reading " << filename;
return;
}
if(std::is_same_v<T, typename reader<T>::value_t >) *begin = my_reader.outp;
else *begin = std::bit_cast<T>(my_reader.outp);
++begin;
}
}
if(!in.eof() && in.fail()){
std::cerr << "error reading " << filename;
return;
}
in.close();
return;
}
template<typename T>
void write_list(T* begin, T* end, const char* filename){
writer<T> my_writer;
std::ofstream outfile(filename, std::ios::out | std::ios::binary | std::ios::trunc);
for(;begin != end; ++begin){
my_writer.convert_num(*begin);
my_writer.write(outfile);
}
}
For example, the following would work as expected:
void write_float_vector(){
std::vector<float> my_floats = {4.981, 832.991, 33.5, 889.56, 99.8191232, 88.192};
std::cout<<"my_floats: " << my_floats<<std::endl;
write_list(&my_floats[0], &my_floats[my_floats.size()], "binary_save/float_try.nt");
}
void read_floats(){
std::vector<float> my_floats(6);
read_list(&my_floats[0], "binary_save/float_try.nt");
std::cout<<"my_floats: " << my_floats<<std::endl;
}
int main(){
write_double_vector();
std::cout<<"reading..."<<std::endl;
read_doubles();
}
However, if it was converted to doubles instead of floats it fails to read the doubles back in correctly. Why is it failing with doubles?
For example, the following fails based on what is outputted from the read_doubles function:
void write_double_vector(){
std::vector<double> my_doubles = {4.981, 832.991, 33.5, 889.56, 99.8191232, 88.192};
std::cout<<"my_doubles: " << my_doubles<<std::endl;
write_list(&my_doubles[0], &my_doubles[my_doubles.size()], "binary_save/double_try.nt");
}
void read_doubles(){
std::vector<double> my_doubles(6);
read_list(&my_doubles[0], "binary_save/double_try.nt");
std::cout<<"my_doubles: " << my_doubles<<std::endl;
}
Additional
If you would like to run the code yourself, I added these helper functions, and used the following headers to make it easier to reproduce:
#include <cstddef>
#include <cstdint>
#include <ios>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <array>
#include <bit>
template<typename T>
std::ostream& operator<<(std::ostream& os, const std::vector<T>& v){
os << "{";
for(uint32_t i = 0; i < v.size()-1; ++i)
os << v[i]<<',';
os << v.back() << "}";
return os;
}
Building a
floatordoublecharacter-by-character can cause a trap representation so don't do that. Replace the_arrdefinition withstd::array<char, Size> _arr;and then usein.read+std::memcpy:Example:
Demo (originally from Paddy)