Date: Mon, 14 Dec 2020 19:05:05 -0500
Example code using bittensor might both explain the API and show motivation for its existence.
Sent from my BlackBerry portable Babbage Device
From: Pablo Ruiz via Std-Proposals Sent: Monday, December 14, 2020 2:23 PM To: std-proposals_at_[hidden] Reply To: std-proposals_at_[hidden] Cc: Pablo Ruiz Subject: [std-proposals] Arbitrary dimensional bittensor extending bitset. |
Hi everyone,
These past days I have been playing with some bit manipulation by using the c++20 std and extending from it.
What I have found is the nice templated class std::bitset<N> and the utility functions in the numerics library (added in c++20), if there is anything more in the standard let me know.
What I would want to propose is for an extension, you could say a generalization, of this std::bitset class to arbitrary dimensions. Namely what I have called a bittensor. The idea would be to use the already existing std::bitset for storing the data and defining over it an easy to use interface for dealing with n-dimensional binary tensors, instead of relying in manual bit fiddling (that one should also be able to use) one could transform this tensors in a higher-level way, with the same performance and size (this would be the ideal).
Here I put a very simple sketch of what I think it should look like:
___________________________________________________________________________
template<std::size_t... N_i>
struct bittensor
{
std::bitset< (... * N_i) > _data;
constexpr static std::size_t _dims[] = {N_i ... };
constexpr bittensor () noexcept {};
constexpr bittensor (const std::bitset< (... * N_i) >& _val) noexcept : _data(_val) {};
constexpr bittensor (const bittensor< N_i ... >& bt) noexcept : _data(bt._data) {};
template<typename T>
constexpr bittensor (const T& _val) noexcept
{_data = std::bitset< (... * N_i) > (_val);}
//TODO -> specify types of constructor as in bitset or just
// remain with this general templated constructor that
// seems to work as expected.
//Now come the operators defined on the class bittensor.
//Mainly this is a wrapper that just sends the job
//to the 1d version bitset that we are using as _data.
constexpr inline bittensor& operator&= (const bittensor& rhs) noexcept { _data &= rhs._data; return *this;};
constexpr inline bittensor& operator|= (const bittensor& rhs) noexcept { _data |= rhs._data; return *this;};
constexpr inline bittensor& operator^= (const bittensor& rhs) noexcept { _data ^= rhs._data; return *this;};
constexpr inline bittensor& operator<<= (std::size_t pos) noexcept { _data <<= pos; return *this;};
constexpr inline bittensor& operator>>= (std::size_t pos) noexcept { _data >>= pos; return *this;};
constexpr inline bittensor operator~() const noexcept { return bittensor< N_i ... > (~_data);};
constexpr inline bittensor operator<< (std::size_t pos) const noexcept { return bittensor< N_i ... > (_data << pos);};
constexpr inline bittensor operator>> (std::size_t pos) const noexcept { return bittensor< N_i ... > (_data >> pos);};
constexpr inline bool operator== (const bittensor& rhs) const noexcept { return _data == rhs._data;};
constexpr inline bool operator!= (const bittensor& rhs) const noexcept { return _data != rhs._data;};
//Bit access, mainly the ones given by the bitset class
constexpr inline bool operator[] (std::size_t pos) const noexcept { return _data[pos];};
constexpr inline std::size_t count () const noexcept { return _data.count();};
constexpr inline std::size_t size () const noexcept { return _data.size();};
constexpr inline bool test (std::size_t pos) const { return _data.test(pos);};
constexpr inline bool any () const noexcept { return _data.any();};
constexpr inline bool none () const noexcept { return _data.none();};
constexpr inline bool all () const noexcept { return _data.all();};
//Bit operations, mainly the ones given by the bitset class
constexpr inline bittensor& set () noexcept { _data.set(); return *this;};
constexpr inline bittensor& set (std::size_t pos, bool val = true) { _data.set(pos, val); return *this;};
constexpr inline bittensor& reset () noexcept { _data.reset(); return *this;};
constexpr inline bittensor& reset (std::size_t pos) { _data.reset(pos); return *this;};
constexpr inline bittensor& flip () noexcept { _data.flip(); return *this;};
constexpr inline bittensor& flip (std::size_t pos) { _data.flip(pos); return *this;};
//Type conversions, mainly the ones given by the bitset class
constexpr inline unsigned long to_ulong () const { return _data.to_ulong;};
constexpr inline unsigned long long to_ullong () const { return _data.to_ullong;};
};
struct bittensor
{
std::bitset< (... * N_i) > _data;
constexpr static std::size_t _dims[] = {N_i ... };
constexpr bittensor () noexcept {};
constexpr bittensor (const std::bitset< (... * N_i) >& _val) noexcept : _data(_val) {};
constexpr bittensor (const bittensor< N_i ... >& bt) noexcept : _data(bt._data) {};
template<typename T>
constexpr bittensor (const T& _val) noexcept
{_data = std::bitset< (... * N_i) > (_val);}
//TODO -> specify types of constructor as in bitset or just
// remain with this general templated constructor that
// seems to work as expected.
//Now come the operators defined on the class bittensor.
//Mainly this is a wrapper that just sends the job
//to the 1d version bitset that we are using as _data.
constexpr inline bittensor& operator&= (const bittensor& rhs) noexcept { _data &= rhs._data; return *this;};
constexpr inline bittensor& operator|= (const bittensor& rhs) noexcept { _data |= rhs._data; return *this;};
constexpr inline bittensor& operator^= (const bittensor& rhs) noexcept { _data ^= rhs._data; return *this;};
constexpr inline bittensor& operator<<= (std::size_t pos) noexcept { _data <<= pos; return *this;};
constexpr inline bittensor& operator>>= (std::size_t pos) noexcept { _data >>= pos; return *this;};
constexpr inline bittensor operator~() const noexcept { return bittensor< N_i ... > (~_data);};
constexpr inline bittensor operator<< (std::size_t pos) const noexcept { return bittensor< N_i ... > (_data << pos);};
constexpr inline bittensor operator>> (std::size_t pos) const noexcept { return bittensor< N_i ... > (_data >> pos);};
constexpr inline bool operator== (const bittensor& rhs) const noexcept { return _data == rhs._data;};
constexpr inline bool operator!= (const bittensor& rhs) const noexcept { return _data != rhs._data;};
//Bit access, mainly the ones given by the bitset class
constexpr inline bool operator[] (std::size_t pos) const noexcept { return _data[pos];};
constexpr inline std::size_t count () const noexcept { return _data.count();};
constexpr inline std::size_t size () const noexcept { return _data.size();};
constexpr inline bool test (std::size_t pos) const { return _data.test(pos);};
constexpr inline bool any () const noexcept { return _data.any();};
constexpr inline bool none () const noexcept { return _data.none();};
constexpr inline bool all () const noexcept { return _data.all();};
//Bit operations, mainly the ones given by the bitset class
constexpr inline bittensor& set () noexcept { _data.set(); return *this;};
constexpr inline bittensor& set (std::size_t pos, bool val = true) { _data.set(pos, val); return *this;};
constexpr inline bittensor& reset () noexcept { _data.reset(); return *this;};
constexpr inline bittensor& reset (std::size_t pos) { _data.reset(pos); return *this;};
constexpr inline bittensor& flip () noexcept { _data.flip(); return *this;};
constexpr inline bittensor& flip (std::size_t pos) { _data.flip(pos); return *this;};
//Type conversions, mainly the ones given by the bitset class
constexpr inline unsigned long to_ulong () const { return _data.to_ulong;};
constexpr inline unsigned long long to_ullong () const { return _data.to_ullong;};
};
//non-member functions
template<std::size_t... N_i>
bittensor< N_i ... > operator& (const bittensor< N_i ... >& lhs, const bittensor< N_i ... >& rhs) noexcept { return bittensor< N_i ... > (lhs._data & rhs._data);}
template<std::size_t... N_i >
bittensor< N_i ... > operator| (const bittensor< N_i ... >& lhs, const bittensor< N_i ... >& rhs) noexcept { return bittensor< N_i ... > (lhs._data | rhs._data);}
template<std::size_t... N_i >
bittensor< N_i ... > operator^ (const bittensor< N_i ... >& lhs, const bittensor< N_i ... >& rhs) noexcept { return bittensor< N_i ... > (lhs._data ^ rhs._data);}
//And now come the iostream inserters and extractors for printing _data (for the moment) to a certain stream
template<class charT, class traits, std::size_t... N_i>
std::basic_istream<charT, traits>& operator>> (std::basic_istream<charT, traits>& is, bittensor< N_i ... >& rhs) { is >> rhs._data; return is;}
template<class charT, class traits, std::size_t... N_i>
std::basic_ostream<charT, traits>& operator<< (std::basic_ostream<charT, traits>& os, const bittensor< N_i ... >& rhs) { os << rhs._data; return os;}
bittensor< N_i ... > operator& (const bittensor< N_i ... >& lhs, const bittensor< N_i ... >& rhs) noexcept { return bittensor< N_i ... > (lhs._data & rhs._data);}
template<std::size_t... N_i >
bittensor< N_i ... > operator| (const bittensor< N_i ... >& lhs, const bittensor< N_i ... >& rhs) noexcept { return bittensor< N_i ... > (lhs._data | rhs._data);}
template<std::size_t... N_i >
bittensor< N_i ... > operator^ (const bittensor< N_i ... >& lhs, const bittensor< N_i ... >& rhs) noexcept { return bittensor< N_i ... > (lhs._data ^ rhs._data);}
//And now come the iostream inserters and extractors for printing _data (for the moment) to a certain stream
template<class charT, class traits, std::size_t... N_i>
std::basic_istream<charT, traits>& operator>> (std::basic_istream<charT, traits>& is, bittensor< N_i ... >& rhs) { is >> rhs._data; return is;}
template<class charT, class traits, std::size_t... N_i>
std::basic_ostream<charT, traits>& operator<< (std::basic_ostream<charT, traits>& os, const bittensor< N_i ... >& rhs) { os << rhs._data; return os;}
//Add hashing support (based on the one given by bitset)
namespace std
{
template<std::size_t... N_i>
struct hash<bittensor< N_i ... >>
{
hash () noexcept {};
size_t operator() (const bittensor< N_i ... >& bt) const noexcept
{ return hash< std::bitset< (... * N_i) > > ()(bt._data);}
};
template<>
struct hash<bittensor<0>>
{
hash () noexcept {};
size_t operator() (const bittensor<0>& bt) const noexcept {return static_cast<size_t> (bt[0]);}
};
}
{
template<std::size_t... N_i>
struct hash<bittensor< N_i ... >>
{
hash () noexcept {};
size_t operator() (const bittensor< N_i ... >& bt) const noexcept
{ return hash< std::bitset< (... * N_i) > > ()(bt._data);}
};
template<>
struct hash<bittensor<0>>
{
hash () noexcept {};
size_t operator() (const bittensor<0>& bt) const noexcept {return static_cast<size_t> (bt[0]);}
};
}
___________________________________________________________________________
This is far from something really useful but I thought it may help to introduce more explicitly the idea I have in mind. I hope there are not much mistakes in the code (which compiles well in g++-10 as a .h file) and to receive your thoughts about this extension of the std::bitset. I think that it c++ wants to incorporate more math libraries into its standard (as it seems to be doing) incorporating something like this is a must (look at the numpy arrays for the python case for instance). And something that we may further extend by templating on the 1d data structure (here std::bitset) to an arbitrary vector-space-like structure, but this seems to be a really harder project.
Regards,
Pablo Ruiz.
Received on 2020-12-14 18:05:13