kokkos / mdspan

Reference implementation of mdspan targeting C++23
Other
417 stars 69 forks source link

Not compatible with std::vector<bool> #68

Closed Babalion closed 3 years ago

Babalion commented 3 years ago

Consider the following code-snippet:

std::vector<bool> boolVec{1,2.3,4,5,6,7,8,9,10,11,12,13,14,15,16};
std::experimental::mdspan m{v.data(),std::experimental::extents{2,4}};

Obviously I'm trying to create a mdspan of a std::vector<bool>, but the following error occurs with CLang 12.0.1:

No viable constructor or deduction guide for deduction of template arguments of 'mdspan'
mhoemmen commented 3 years ago

This is expected behavior, due to std::vector<bool> being a special case.

  1. std::vector<bool> does not necessarily store its entries as an array of bool, so you can't just use the default mdspan accessor.
  2. The C++ Standard does not require that std::vector<bool> even have a .data() method.

Regarding (1), it should be possible to write a custom accessor for mdspan that would view an existing std::vector<bool>'s elements. You can't use the default mdspan accessor, because it would incorrectly assume that you have a contiguous array of bool. That's not how std::vector<bool> works. It's more like a bitset, that has the freedom to store each Boolean value in a bit. The type that nonconst operator[] returns isn't bool&, it's a special "proxy reference" type (since C++ doesn't have the idea of a "reference to a bit in a word").

mhoemmen commented 3 years ago

@crtrott check this out

template<class T>
requires(std::is_same_v<T, bool> || std::is_same_v<T, const bool>)
class VectorBoolAccessor {
  static constexpr bool is_const = std::is_same_v<T, const bool>;

public:
  using element_type = T;
  using reference = std::conditional_t<is_const,
    std::vector<bool>::const_reference,
    std::vector<bool>::reference>;
  struct pointer {
    using raw_pointer = std::conditional_t<is_const, const std::vector<bool>*, std::vector<bool>*>;

    raw_pointer ptr{};
    std::size_t offset = 0;

    pointer() = default;
    pointer(raw_pointer thePtr, std::size_t theOffset = 0) : ptr(thePtr), offset(theOffset) {}
  };
  using offset_policy = VectorBoolAccessor<T>;

  VectorBoolAccessor() = default;

  template<class U>
  requires(std::is_same_v<T, U> || is_const)
  VectorBoolAccessor(const VectorBoolAccessor<U>&) {}

  reference access(pointer p, std::size_t i) const {
    return (*(p.ptr))[p.offset + i];
  }
  offset_policy::pointer offset(pointer p, std::size_t i) const {
    return {p.ptr, p.offset + i};
  }
};
crtrott commented 3 years ago

Accessors are awesome aren't they :-)