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

//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]);}
};
}


___________________________________________________________________________

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.