218 lines
8.3 KiB
C
218 lines
8.3 KiB
C
|
// Copyright 2020 The Dawn Authors
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
#ifndef COMMON_TYPEDINTEGER_H_
|
||
|
#define COMMON_TYPEDINTEGER_H_
|
||
|
|
||
|
#include "common/Assert.h"
|
||
|
|
||
|
#include <limits>
|
||
|
#include <type_traits>
|
||
|
|
||
|
// TypedInteger is helper class that provides additional type safety in Debug.
|
||
|
// - Integers of different (Tag, BaseIntegerType) may not be used interoperably
|
||
|
// - Allows casts only to the underlying type.
|
||
|
// - Integers of the same (Tag, BaseIntegerType) may be compared or assigned.
|
||
|
// This class helps ensure that the many types of indices in Dawn aren't mixed up and used
|
||
|
// interchangably.
|
||
|
// In Release builds, when DAWN_ENABLE_ASSERTS is not defined, TypedInteger is a passthrough
|
||
|
// typedef of the underlying type.
|
||
|
//
|
||
|
// Example:
|
||
|
// using UintA = TypedInteger<struct TypeA, uint32_t>;
|
||
|
// using UintB = TypedInteger<struct TypeB, uint32_t>;
|
||
|
//
|
||
|
// in Release:
|
||
|
// using UintA = uint32_t;
|
||
|
// using UintB = uint32_t;
|
||
|
//
|
||
|
// in Debug:
|
||
|
// using UintA = detail::TypedIntegerImpl<struct TypeA, uint32_t>;
|
||
|
// using UintB = detail::TypedIntegerImpl<struct TypeB, uint32_t>;
|
||
|
//
|
||
|
// Assignment, construction, comparison, and arithmetic with TypedIntegerImpl are allowed
|
||
|
// only for typed integers of exactly the same type. Further, they must be
|
||
|
// created / cast explicitly; there is no implicit conversion.
|
||
|
//
|
||
|
// UintA a(2);
|
||
|
// uint32_t aValue = static_cast<uint32_t>(a);
|
||
|
//
|
||
|
namespace detail {
|
||
|
template <typename Tag, typename T>
|
||
|
class TypedIntegerImpl;
|
||
|
} // namespace detail
|
||
|
|
||
|
template <typename Tag, typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
|
||
|
#if defined(DAWN_ENABLE_ASSERTS)
|
||
|
using TypedInteger = detail::TypedIntegerImpl<Tag, T>;
|
||
|
#else
|
||
|
using TypedInteger = T;
|
||
|
#endif
|
||
|
|
||
|
namespace detail {
|
||
|
template <typename Tag, typename T>
|
||
|
class TypedIntegerImpl {
|
||
|
static_assert(std::is_integral<T>::value, "TypedInteger must be integral");
|
||
|
T mValue;
|
||
|
|
||
|
public:
|
||
|
constexpr TypedIntegerImpl() : mValue(0) {
|
||
|
}
|
||
|
|
||
|
// Construction from non-narrowing integral types.
|
||
|
template <typename I,
|
||
|
typename = std::enable_if_t<
|
||
|
std::is_integral<I>::value &&
|
||
|
std::numeric_limits<I>::max() <= std::numeric_limits<T>::max() &&
|
||
|
std::numeric_limits<I>::min() >= std::numeric_limits<T>::min()>>
|
||
|
explicit constexpr TypedIntegerImpl(I rhs) : mValue(static_cast<T>(rhs)) {
|
||
|
}
|
||
|
|
||
|
// Allow explicit casts only to the underlying type. If you're casting out of an
|
||
|
// TypedInteger, you should know what what you're doing, and exactly what type you
|
||
|
// expect.
|
||
|
explicit constexpr operator T() const {
|
||
|
return static_cast<T>(this->mValue);
|
||
|
}
|
||
|
|
||
|
// Same-tag TypedInteger comparison operators
|
||
|
#define TYPED_COMPARISON(op) \
|
||
|
constexpr bool operator op(const TypedIntegerImpl& rhs) const { \
|
||
|
return mValue op rhs.mValue; \
|
||
|
}
|
||
|
TYPED_COMPARISON(<)
|
||
|
TYPED_COMPARISON(<=)
|
||
|
TYPED_COMPARISON(>)
|
||
|
TYPED_COMPARISON(>=)
|
||
|
TYPED_COMPARISON(==)
|
||
|
TYPED_COMPARISON(!=)
|
||
|
#undef TYPED_COMPARISON
|
||
|
|
||
|
// Increment / decrement operators for for-loop iteration
|
||
|
constexpr TypedIntegerImpl& operator++() {
|
||
|
ASSERT(this->mValue < std::numeric_limits<T>::max());
|
||
|
++this->mValue;
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
constexpr TypedIntegerImpl operator++(int) {
|
||
|
TypedIntegerImpl ret = *this;
|
||
|
|
||
|
ASSERT(this->mValue < std::numeric_limits<T>::max());
|
||
|
++this->mValue;
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
constexpr TypedIntegerImpl& operator--() {
|
||
|
assert(this->mValue > std::numeric_limits<T>::min());
|
||
|
--this->mValue;
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
constexpr TypedIntegerImpl operator--(int) {
|
||
|
TypedIntegerImpl ret = *this;
|
||
|
|
||
|
ASSERT(this->mValue > std::numeric_limits<T>::min());
|
||
|
--this->mValue;
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
template <typename T2 = T>
|
||
|
constexpr std::enable_if_t<std::is_signed<T2>::value, TypedIntegerImpl> operator-() const {
|
||
|
static_assert(std::is_same<T, T2>::value, "");
|
||
|
// The negation of the most negative value cannot be represented.
|
||
|
ASSERT(this->mValue != std::numeric_limits<T>::min());
|
||
|
return TypedIntegerImpl(-this->mValue);
|
||
|
}
|
||
|
|
||
|
template <typename T2 = T>
|
||
|
constexpr std::enable_if_t<std::is_unsigned<T2>::value, TypedIntegerImpl> operator+(
|
||
|
TypedIntegerImpl rhs) const {
|
||
|
static_assert(std::is_same<T, T2>::value, "");
|
||
|
// Overflow would wrap around
|
||
|
ASSERT(this->mValue + rhs.mValue >= this->mValue);
|
||
|
|
||
|
return TypedIntegerImpl(this->mValue + rhs.mValue);
|
||
|
}
|
||
|
|
||
|
template <typename T2 = T>
|
||
|
constexpr std::enable_if_t<std::is_unsigned<T2>::value, TypedIntegerImpl> operator-(
|
||
|
TypedIntegerImpl rhs) const {
|
||
|
static_assert(std::is_same<T, T2>::value, "");
|
||
|
// Overflow would wrap around
|
||
|
ASSERT(this->mValue - rhs.mValue <= this->mValue);
|
||
|
return TypedIntegerImpl(this->mValue - rhs.mValue);
|
||
|
}
|
||
|
|
||
|
template <typename T2 = T>
|
||
|
constexpr std::enable_if_t<std::is_signed<T2>::value, TypedIntegerImpl> operator+(
|
||
|
TypedIntegerImpl rhs) const {
|
||
|
static_assert(std::is_same<T, T2>::value, "");
|
||
|
if (this->mValue > 0) {
|
||
|
// rhs is positive: |rhs| is at most the distance between max and |this|.
|
||
|
// rhs is negative: (positive + negative) won't overflow
|
||
|
ASSERT(rhs.mValue <= std::numeric_limits<T>::max() - this->mValue);
|
||
|
} else {
|
||
|
// rhs is postive: (negative + positive) won't underflow
|
||
|
// rhs is negative: |rhs| isn't less than the (negative) distance between min
|
||
|
// and |this|
|
||
|
ASSERT(rhs.mValue >= std::numeric_limits<T>::min() - this->mValue);
|
||
|
}
|
||
|
return TypedIntegerImpl(this->mValue + rhs.mValue);
|
||
|
}
|
||
|
|
||
|
template <typename T2 = T>
|
||
|
constexpr std::enable_if_t<std::is_signed<T2>::value, TypedIntegerImpl> operator-(
|
||
|
TypedIntegerImpl rhs) const {
|
||
|
static_assert(std::is_same<T, T2>::value, "");
|
||
|
if (this->mValue > 0) {
|
||
|
// rhs is positive: positive minus positive won't overflow
|
||
|
// rhs is negative: |rhs| isn't less than the (negative) distance between |this|
|
||
|
// and max.
|
||
|
ASSERT(rhs.mValue >= this->mValue - std::numeric_limits<T>::max());
|
||
|
} else {
|
||
|
// rhs is positive: |rhs| is at most the distance between min and |this|
|
||
|
// rhs is negative: negative minus negative won't overflow
|
||
|
ASSERT(rhs.mValue <= this->mValue - std::numeric_limits<T>::min());
|
||
|
}
|
||
|
return TypedIntegerImpl(this->mValue - rhs.mValue);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
} // namespace detail
|
||
|
|
||
|
namespace std {
|
||
|
template <typename Tag, typename T>
|
||
|
class numeric_limits<detail::TypedIntegerImpl<Tag, T>> : public numeric_limits<T> {
|
||
|
public:
|
||
|
static detail::TypedIntegerImpl<Tag, T> max() noexcept {
|
||
|
return detail::TypedIntegerImpl<Tag, T>(std::numeric_limits<T>::max());
|
||
|
}
|
||
|
static detail::TypedIntegerImpl<Tag, T> min() noexcept {
|
||
|
return detail::TypedIntegerImpl<Tag, T>(std::numeric_limits<T>::min());
|
||
|
}
|
||
|
};
|
||
|
|
||
|
template <typename Tag, typename T>
|
||
|
class hash<detail::TypedIntegerImpl<Tag, T>> : private hash<T> {
|
||
|
public:
|
||
|
size_t operator()(detail::TypedIntegerImpl<Tag, T> value) const {
|
||
|
return hash<T>::operator()(static_cast<T>(value));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
} // namespace std
|
||
|
|
||
|
#endif // COMMON_TYPEDINTEGER_H_
|