Hello, 

As Arthur said, i don't really see the need for this in the standard library. 
More generally : there is not as of yet a defacto standard for multidimensional containers, which is why i don't think we're going to get mdspan anytime soon. Case in point : a very popular md container library in the scientific C++ community is the xtensor library, and in it dimensions are handled in a very different way (one type for static shape, one type for static rank, one type fully dynamic, everything is flatly iterable) than the mdspan proposal.
AFAIK nothing has been written on the pros and cons of both approaches.

-Jean-Baptiste 

Le mar. 15 déc. 2020 à 01:06, Tony V E via Std-Proposals <std-proposals@lists.isocpp.org> a écrit :
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
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;};
};
//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@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals