我正在尝试编写一个程序,可以获取特定类型的列表,例如
double
或 float
并将其转换为字节并将其写入一个可以转换回原始列表的文件。我想出了以下 struct
和功能。它适用于 float
列表,但是,如果它是 doubles
列表,则不起作用,我正在尝试理解原因。
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;
}
};
以下可用于成功将
float
或 uint32_t
写入文本文件。以下可用于读取其中一个数字:
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;
}
};
然后我使用以下函数来迭代文本文件并读取/写入该文件:
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);
}
}
例如,以下内容将按预期工作:
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();
}
但是,如果将其转换为
double
而不是 float
,则无法正确读回双精度数。为什么双打会失败?
例如,根据
read_doubles
函数的输出,以下内容将失败:
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;
}
如果您想自己运行代码,我添加了这些辅助函数,并使用以下标头以使其更容易重现:
#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;
}
逐个字符构建
float
或 double
可能会导致陷阱表示,因此请勿这样做。将 _arr
定义替换为 std::array<char, Size> _arr;
,然后使用 in.read
+ std::memcpy
:
示例:
#include <cstring>
std::array<char, Size> _arr;
bool add_nums(std::istream& in) {
return static_cast<bool>(in.read(_arr.data(), Size));
}
bool convert() {
std::memcpy(&outp, _arr.data(), Size);
return true;
}