C++ Logo

std-proposals

Advanced search

Re: Arbitrary dimensional bittensor extending bitset.

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Mon, 14 Dec 2020 17:07:57 -0500
Quick thoughts and code-review nits:
- I don't think the Standard needs this. It'd make a neat Github project
though.
- Your `operator[]` is nonsensical; you need P2128
<https://github.com/cplusplus/papers/issues/845>to land first, and then you
can make a proper multidimensional operator[].
- Consider providing a multidimensional `bittensor.at(x,y,z)` as a
placeholder.
- Your `size()` getter is nonsensical.
- In general, multidimensional things are hard, because C++ loves iteration
and multidimensional things aren't obviously iterable. Have you looked into
the forever-ongoing "mdspan" proposal? They might have some ideas or
idioms you could copy.

–Arthur




On Mon, Dec 14, 2020 at 2:23 PM Pablo Ruiz via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> 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.
>
>
>
>
>
>
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>

Received on 2020-12-14 16:08:11