Commit 648839c3 authored by Kenton Varda's avatar Kenton Varda

Enforce transitive constness for Maybe<T&> and Array<T>.

parent b7e1266b
......@@ -86,12 +86,12 @@ public:
inline Builder(decltype(nullptr)): ArrayPtr<byte>(nullptr) {}
inline Builder(byte* value, size_t size): ArrayPtr<byte>(value, size) {}
inline Builder(kj::Array<byte>& value): ArrayPtr<byte>(value) {}
inline Builder(const ArrayPtr<byte>& value): ArrayPtr<byte>(value) {}
inline Builder(ArrayPtr<byte>& value): ArrayPtr<byte>(value) {}
inline Data::Reader asReader() const { return Data::Reader(*this); }
};
class Text::Builder {
class Text::Builder: public kj::DisallowConstCopy {
// Basically identical to kj::StringPtr, except that the contents are non-const.
public:
......@@ -104,8 +104,10 @@ public:
inline Reader asReader() const { return Reader(content.begin(), content.size()); }
inline operator kj::ArrayPtr<char>() const;
inline kj::ArrayPtr<char> asArray() const;
inline operator kj::ArrayPtr<char>();
inline kj::ArrayPtr<char> asArray();
inline operator kj::ArrayPtr<const char>() const;
inline kj::ArrayPtr<const char> asArray() const;
// Result does not include NUL terminator.
inline operator kj::StringPtr() const;
......@@ -119,8 +121,10 @@ public:
inline char operator[](size_t index) const { return content[index]; }
inline char* begin() const { return content.begin(); }
inline char* end() const { return content.end() - 1; }
inline char* begin() { return content.begin(); }
inline char* end() { return content.end() - 1; }
inline const char* begin() const { return content.begin(); }
inline const char* end() const { return content.end() - 1; }
inline bool operator==(decltype(nullptr)) const { return content.size() <= 1; }
inline bool operator!=(decltype(nullptr)) const { return content.size() > 1; }
......@@ -132,8 +136,10 @@ public:
inline bool operator<=(Builder other) const { return asString() <= other.asString(); }
inline bool operator>=(Builder other) const { return asString() >= other.asString(); }
inline Builder slice(size_t start) const;
inline kj::ArrayPtr<char> slice(size_t start, size_t end) const;
inline kj::StringPtr slice(size_t start) const;
inline kj::ArrayPtr<const char> slice(size_t start, size_t end) const;
inline Builder slice(size_t start);
inline kj::ArrayPtr<char> slice(size_t start, size_t end);
// A string slice is only NUL-terminated if it is a suffix, so slice() has a one-parameter
// version that assumes end = size().
......@@ -160,18 +166,33 @@ inline kj::StringPtr Text::Builder::asString() const {
return kj::StringPtr(content.begin(), content.size() - 1);
}
inline Text::Builder::operator kj::ArrayPtr<char>() const {
inline Text::Builder::operator kj::ArrayPtr<char>() {
return content.slice(0, content.size() - 1);
}
inline kj::ArrayPtr<char> Text::Builder::asArray() const {
inline kj::ArrayPtr<char> Text::Builder::asArray() {
return content.slice(0, content.size() - 1);
}
inline Text::Builder Text::Builder::slice(size_t start) const {
inline Text::Builder::operator kj::ArrayPtr<const char>() const {
return content.slice(0, content.size() - 1);
}
inline kj::ArrayPtr<const char> Text::Builder::asArray() const {
return content.slice(0, content.size() - 1);
}
inline kj::StringPtr Text::Builder::slice(size_t start) const {
return asReader().slice(start);
}
inline kj::ArrayPtr<const char> Text::Builder::slice(size_t start, size_t end) const {
return content.slice(start, end);
}
inline Text::Builder Text::Builder::slice(size_t start) {
return Text::Builder(content.slice(start, content.size()));
}
inline kj::ArrayPtr<char> Text::Builder::slice(size_t start, size_t end) const {
inline kj::ArrayPtr<char> Text::Builder::slice(size_t start, size_t end) {
return content.slice(start, end);
}
......
......@@ -51,16 +51,6 @@ class SegmentBuilder;
// =============================================================================
struct DisallowConstCopy {
DisallowConstCopy() = default;
DisallowConstCopy(DisallowConstCopy&);
DisallowConstCopy(DisallowConstCopy&&) = default;
DisallowConstCopy& operator=(DisallowConstCopy&);
DisallowConstCopy& operator=(DisallowConstCopy&&) = default;
};
inline DisallowConstCopy::DisallowConstCopy(DisallowConstCopy&) = default;
inline DisallowConstCopy& DisallowConstCopy::operator=(DisallowConstCopy&) = default;
enum class FieldSize: uint8_t {
// TODO(cleanup): Rename to FieldLayout or maybe ValueLayout.
......@@ -300,7 +290,7 @@ private:
T value;
};
class StructBuilder: public DisallowConstCopy {
class StructBuilder: public kj::DisallowConstCopy {
public:
inline StructBuilder(): segment(nullptr), data(nullptr), pointers(nullptr), bit0Offset(0) {}
......@@ -520,7 +510,7 @@ private:
// -------------------------------------------------------------------
class ListBuilder: public DisallowConstCopy {
class ListBuilder: public kj::DisallowConstCopy {
public:
inline ListBuilder()
: segment(nullptr), ptr(nullptr), elementCount(0 * ELEMENTS),
......
......@@ -35,7 +35,7 @@ inline std::ostream& operator<<(std::ostream& os, const Data::Reader& value) {
return os.write(reinterpret_cast<const char*>(value.begin()), value.size());
}
inline std::ostream& operator<<(std::ostream& os, const Data::Builder& value) {
return os.write(reinterpret_cast<char*>(value.begin()), value.size());
return os.write(reinterpret_cast<const char*>(value.begin()), value.size());
}
inline std::ostream& operator<<(std::ostream& os, const Text::Reader& value) {
return os.write(value.begin(), value.size());
......
......@@ -152,6 +152,23 @@ TEST(Common, Maybe) {
}
}
TEST(Common, MaybeConstness) {
int i;
Maybe<int&> mi = i;
const Maybe<int&> cmi = mi;
// const Maybe<int&> cmi2 = cmi; // shouldn't compile! Transitive const violation.
Maybe<const int&> mci = mi;
const Maybe<const int&> cmci = mci;
const Maybe<const int&> cmci2 = cmci;
KJ_IF_MAYBE(i2, cmci2) {
EXPECT_EQ(&i, i2);
} else {
ADD_FAILURE();
}
}
class Foo {
public:
virtual ~Foo() {}
......
......@@ -202,6 +202,56 @@ T instance() noexcept;
// Like std::declval, but doesn't transform T into an rvalue reference. If you want that, specify
// instance<T&&>().
struct DisallowConstCopy {
// Inherit from this, or declare a member variable of this type, to prevent the class from being
// copyable from a const reference -- instead, it will only be copyable from non-const references.
// This is useful for enforcing transitive constness of contained pointers.
//
// For example, say you have a type T which contains a pointer. T has non-const methods which
// modify the value at that pointer, but T's const methods are designed to allow reading only.
// Unfortunately, if T has a regular copy constructor, someone can simply make a copy of T and
// then use it to modify the pointed-to value. However, if T inherits DisallowConstCopy, then
// callers will only be able to copy non-const instances of T. Ideally, there is some
// parallel type ImmutableT which is like a version of T that only has const methods, and can
// be copied from a const T.
//
// Note that due to C++ rules about implicit copy constructors and assignment operators, any
// type that contains or inherits from a type that disallows const copies will also automatically
// disallow const copies. Hey, cool, that's exactly what we want.
DisallowConstCopy() = default;
DisallowConstCopy(DisallowConstCopy&);
DisallowConstCopy(DisallowConstCopy&&) = default;
DisallowConstCopy& operator=(DisallowConstCopy&);
DisallowConstCopy& operator=(DisallowConstCopy&&) = default;
};
// Apparently these cannot be defaulted inside the class due to some obscure C++ rule.
inline DisallowConstCopy::DisallowConstCopy(DisallowConstCopy&) = default;
inline DisallowConstCopy& DisallowConstCopy::operator=(DisallowConstCopy&) = default;
template <typename T>
struct DisallowConstCopyIfNotConst: public DisallowConstCopy {
// Inherit from this when implementing a template that contains a pointer to T and which should
// enforce transitive constness. If T is a const type, this has no effect. Otherwise, it is
// an alias for DisallowConstCopy.
};
template <typename T>
struct DisallowConstCopyIfNotConst<const T> {};
template <typename T> struct EnableIfNotConst_ { typedef T Type; };
template <typename T> struct EnableIfNotConst_<const T>;
template <typename T> using EnableIfNotConst = typename EnableIfNotConst_<T>::Type;
template <typename T> struct EnableIfConst_;
template <typename T> struct EnableIfConst_<const T> { typedef T Type; };
template <typename T> using EnableIfConst = typename EnableIfConst_<T>::Type;
template <typename T, typename U> struct EnableIfDifferent_ { typedef int Type; };
template <typename T> struct EnableIfDifferent_<T, T> {};
template <typename T, typename U> using EnableIfDifferent = typename EnableIfDifferent_<T, U>::Type;
// =======================================================================================
// Equivalents to std::move() and std::forward(), since these are very commonly needed and the
// std header <utility> pulls in lots of other stuff.
......@@ -428,8 +478,6 @@ public:
Maybe(decltype(nullptr)) noexcept: ptr(nullptr) {}
~Maybe() noexcept {}
inline Maybe& operator=(Maybe&& other) { ptr = kj::mv(other.ptr); return *this; }
inline Maybe& operator=(const Maybe& other) { ptr = other.ptr; return *this; }
......@@ -471,19 +519,24 @@ private:
};
template <typename T>
class Maybe<T&> {
class Maybe<T&>: public DisallowConstCopyIfNotConst<T> {
public:
Maybe(): ptr(nullptr) {}
Maybe() noexcept: ptr(nullptr) {}
Maybe(T& t) noexcept: ptr(&t) {}
Maybe(T* t) noexcept: ptr(t) {}
Maybe(const Maybe& other) noexcept: ptr(other.ptr) {}
template <typename U>
Maybe(const Maybe<U&>& other): ptr(other.ptr) {}
Maybe(decltype(nullptr)) noexcept: ptr(nullptr) {}
~Maybe() noexcept {}
template <typename U>
inline Maybe(Maybe<U&>& other) noexcept: ptr(other.ptr) {}
template <typename U>
inline Maybe(const Maybe<const U&>& other) noexcept: ptr(other.ptr) {}
inline Maybe(decltype(nullptr)) noexcept: ptr(nullptr) {}
inline Maybe& operator=(const Maybe& other) { ptr = other.ptr; return *this; }
inline Maybe& operator=(T& other) noexcept { ptr = &other; }
inline Maybe& operator=(T* other) noexcept { ptr = other; }
template <typename U>
inline Maybe& operator=(Maybe<U&>& other) noexcept { ptr = other.ptr; return *this; }
template <typename U>
inline Maybe& operator=(const Maybe<const U&>& other) noexcept { ptr = other.ptr; return *this; }
inline bool operator==(decltype(nullptr)) const { return ptr == nullptr; }
inline bool operator!=(decltype(nullptr)) const { return ptr != nullptr; }
......@@ -508,14 +561,13 @@ private:
friend U* internal::readMaybe(const Maybe<U&>& maybe);
};
// =======================================================================================
// ArrayPtr
//
// So common that we put it in common.h rather than array.h.
template <typename T>
class ArrayPtr {
class ArrayPtr: public DisallowConstCopyIfNotConst<T> {
// A pointer to an array. Includes a size. Like any pointer, it doesn't own the target data,
// and passing by value only copies the pointer, not the target.
......@@ -533,23 +585,35 @@ public:
}
inline size_t size() const { return size_; }
inline T& operator[](size_t index) const {
inline const T& operator[](size_t index) const {
KJ_IREQUIRE(index < size_, "Out-of-bounds ArrayPtr access.");
return ptr[index];
}
inline T& operator[](size_t index) {
KJ_IREQUIRE(index < size_, "Out-of-bounds ArrayPtr access.");
return ptr[index];
}
inline T* begin() const { return ptr; }
inline T* end() const { return ptr + size_; }
inline T& front() const { return *ptr; }
inline T& back() const { return *(ptr + size_ - 1); }
inline T* begin() { return ptr; }
inline T* end() { return ptr + size_; }
inline T& front() { return *ptr; }
inline T& back() { return *(ptr + size_ - 1); }
inline const T* begin() const { return ptr; }
inline const T* end() const { return ptr + size_; }
inline const T& front() const { return *ptr; }
inline const T& back() const { return *(ptr + size_ - 1); }
inline ArrayPtr slice(size_t start, size_t end) const {
inline ArrayPtr<const T> slice(size_t start, size_t end) const {
KJ_IREQUIRE(start <= end && end <= size_, "Out-of-bounds ArrayPtr::slice().");
return ArrayPtr<const T>(ptr + start, end - start);
}
inline ArrayPtr slice(size_t start, size_t end) {
KJ_IREQUIRE(start <= end && end <= size_, "Out-of-bounds ArrayPtr::slice().");
return ArrayPtr(ptr + start, end - start);
}
inline bool operator==(decltype(nullptr)) { return size_ == 0; }
inline bool operator!=(decltype(nullptr)) { return size_ != 0; }
inline bool operator==(decltype(nullptr)) const { return size_ == 0; }
inline bool operator!=(decltype(nullptr)) const { return size_ != 0; }
inline bool operator==(const ArrayPtr& other) const {
if (size_ != other.size_) return false;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment