A type-safe non-negative index class.
This class serves as an upgrade to the standard practice of passing int
s around as indices. In the common practice, a method that takes indices into multiple collections would have an interface like:
It is possible for a programmer to accidentally switch the two index values in an invocation. This mistake would still be syntactically correct; it will successfully compile but lead to inscrutable run-time errors. The type-safe index provides the same speed and efficiency of passing int
s, but provides compile-time checking. The function would now look like:
and the compiler will catch instances where the order is reversed.
The type-safe index is a stripped down int
. Each uniquely declared index type has the following properties:
int
values.int
(to serve as an index).int
return values. One can even use operands of different index types in such a binary expression. It is the programmer's responsibility to confirm that the resultant int
value has meaning.While there is the concept of an "invalid" index, this only exists to support default construction where appropriate (e.g., using indices in STL containers). Using an invalid index in any operation is considered an error. In Debug build, attempts to compare, increment, decrement, etc. an invalid index will throw an exception.
A function that returns TypeSafeIndex values which need to communicate failure should not use an invalid index. It should return an std::optional<Index>
instead.
It is the designed intent of this class, that indices derived from this class can be passed and returned by value. (Drake's typical calling convention requires passing input arguments by const reference, or by value when moved from. That convention does not apply to this class.)
This is the recommended method to create a unique index type associated with class Foo
:
This references a non-existent, and ultimately anonymous, class FooTag
. This is sufficient to create a unique index type. It is certainly possible to use an existing class (e.g., Foo
). But this provides no functional benefit.
Construction from integral types
C++ will do implicit integer conversions. This allows construction of TypeSafeIndex values with arbitrary integral types. Index values must lie in the range of [0, 2³¹). The constructor will validate the input value (in Debug mode). Ultimately, the caller is responsible for confirming that the values provided lie in the valid range.
Examples of valid and invalid operations
The TypeSafeIndex guarantees that index instances of different types can't be compared or combined. Efforts to do so will cause a compile-time failure. However, comparisons or operations on other types that are convertible to an int will succeed. For example:
As previously stated, the intent of this class is to seamlessly serve as an index into indexed objects (e.g., vector, array, etc.). At the same time, we want to avoid implicit conversions from int to an index. These two design constraints combined lead to a limitation in how TypeSafeIndex instances can be used. Specifically, we've lost a common index pattern:
This pattern no longer works because it requires implicit conversion of int to TypeSafeIndex. Instead, the following pattern needs to be used:
Use with Eigen
At the time of this writing when using the latest Eigen 3.4 preview branch, a TypeSafeIndex cannot be directly used to index into an Eigen::Matrix; the developer must explicitly introduce the int
conversion:
TODO(#15354) We hope to fix this irregularity in the future.
Tag | The name of the tag associated with a class type. The class need not be a defined class. |
#include <drake/common/type_safe_index.h>
Public Member Functions | |
Constructors | |
TypeSafeIndex () | |
Default constructor; the result is an invalid index. More... | |
TypeSafeIndex (int64_t index) | |
Construction from a non-negative int value. More... | |
template<typename U > | |
TypeSafeIndex (const TypeSafeIndex< U > &idx)=delete | |
Disallow construction from another index type. More... | |
TypeSafeIndex (const TypeSafeIndex &)=default | |
TypeSafeIndex (TypeSafeIndex &&other) noexcept | |
Assignment | |
TypeSafeIndex & | operator= (const TypeSafeIndex &)=default |
TypeSafeIndex & | operator= (TypeSafeIndex &&other) noexcept |
Utility methods | |
operator int () const | |
Implicit conversion-to-int operator. More... | |
bool | is_valid () const |
Reports if the index is valid–the only operation on an invalid index that doesn't throw an exception in Debug builds. More... | |
Arithmetic operators | |
const TypeSafeIndex & | operator++ () |
Prefix increment operator. More... | |
TypeSafeIndex | operator++ (int) |
Postfix increment operator. More... | |
const TypeSafeIndex & | operator-- () |
Prefix decrement operator. More... | |
TypeSafeIndex | operator-- (int) |
Postfix decrement operator. More... | |
Compound assignment operators | |
TypeSafeIndex & | operator+= (int i) |
Addition assignment operator. More... | |
TypeSafeIndex< Tag > & | operator+= (const TypeSafeIndex< Tag > &other) |
Allow addition for indices with the same tag. More... | |
template<typename U > | |
TypeSafeIndex< U > & | operator+= (const TypeSafeIndex< U > &u)=delete |
Prevent addition for indices of different tags. More... | |
TypeSafeIndex & | operator-= (int i) |
Subtraction assignment operator. More... | |
TypeSafeIndex< Tag > & | operator-= (const TypeSafeIndex< Tag > &other) |
Allow subtraction for indices with the same tag. More... | |
template<typename U > | |
TypeSafeIndex< U > & | operator-= (const TypeSafeIndex< U > &u)=delete |
Prevent subtraction for indices of different tags. More... | |
Exclusive comparison operators | |
In order to prevent indices of different type being added together or compared against each other, we explicitly include indices of this type, but exclude indices of all other types. This implicitly allows all other objects that can be converted to int types. | |
bool | operator== (const TypeSafeIndex< Tag > &other) const |
Allow equality test with indices of this tag. More... | |
template<typename U > | |
std::enable_if_t< std::is_integral_v< U > &&std::is_unsigned_v< U >, bool > | operator== (const U &value) const |
Allow equality test with unsigned integers. More... | |
template<typename U > | |
bool | operator== (const TypeSafeIndex< U > &u) const =delete |
Prevent equality tests with indices of other tags. More... | |
bool | operator!= (const TypeSafeIndex< Tag > &other) const |
Allow inequality test with indices of this tag. More... | |
template<typename U > | |
std::enable_if_t< std::is_integral_v< U > &&std::is_unsigned_v< U >, bool > | operator!= (const U &value) const |
Allow inequality test with unsigned integers. More... | |
template<typename U > | |
bool | operator!= (const TypeSafeIndex< U > &u) const =delete |
Prevent inequality test with indices of other tags. More... | |
bool | operator< (const TypeSafeIndex< Tag > &other) const |
Allow less than test with indices of this tag. More... | |
template<typename U > | |
std::enable_if_t< std::is_integral_v< U > &&std::is_unsigned_v< U >, bool > | operator< (const U &value) const |
Allow less than test with unsigned integers. More... | |
template<typename U > | |
bool | operator< (const TypeSafeIndex< U > &u) const =delete |
Prevent less than test with indices of other tags. More... | |
bool | operator<= (const TypeSafeIndex< Tag > &other) const |
Allow less than or equals test with indices of this tag. More... | |
template<typename U > | |
std::enable_if_t< std::is_integral_v< U > &&std::is_unsigned_v< U >, bool > | operator<= (const U &value) const |
Allow less than or equals test with unsigned integers. More... | |
template<typename U > | |
bool | operator<= (const TypeSafeIndex< U > &u) const =delete |
Prevent less than or equals test with indices of other tags. More... | |
bool | operator> (const TypeSafeIndex< Tag > &other) const |
Allow greater than test with indices of this tag. More... | |
template<typename U > | |
std::enable_if_t< std::is_integral_v< U > &&std::is_unsigned_v< U >, bool > | operator> (const U &value) const |
Allow greater than test with unsigned integers. More... | |
template<typename U > | |
bool | operator> (const TypeSafeIndex< U > &u) const =delete |
Prevent greater than test with indices of other tags. More... | |
bool | operator>= (const TypeSafeIndex< Tag > &other) const |
Allow greater than or equals test with indices of this tag. More... | |
template<typename U > | |
std::enable_if_t< std::is_integral_v< U > &&std::is_unsigned_v< U >, bool > | operator>= (const U &value) const |
Allow greater than or equals test with unsigned integers. More... | |
template<typename U > | |
bool | operator>= (const TypeSafeIndex< U > &u) const =delete |
Prevent greater than or equals test with indices of other tags. More... | |
Friends | |
template<typename HashAlgorithm > | |
void | hash_append (HashAlgorithm &hasher, const TypeSafeIndex &i) noexcept |
Implements the hash_append generic hashing concept. More... | |
TypeSafeIndex | ( | ) |
Default constructor; the result is an invalid index.
This only exists to serve applications which require a default constructor.
|
explicit |
Construction from a non-negative int
value.
The value must lie in the range of [0, 2³¹). Constructor only promises to test validity in Debug build.
|
delete |
Disallow construction from another index type.
|
default |
|
noexcept |
bool is_valid | ( | ) | const |
Reports if the index is valid–the only operation on an invalid index that doesn't throw an exception in Debug builds.
operator int | ( | ) | const |
Implicit conversion-to-int operator.
bool operator!= | ( | const TypeSafeIndex< Tag > & | other | ) | const |
Allow inequality test with indices of this tag.
std::enable_if_t<std::is_integral_v<U> && std::is_unsigned_v<U>, bool> operator!= | ( | const U & | value | ) | const |
Allow inequality test with unsigned integers.
|
delete |
Prevent inequality test with indices of other tags.
const TypeSafeIndex& operator++ | ( | ) |
Prefix increment operator.
TypeSafeIndex operator++ | ( | int | ) |
Postfix increment operator.
TypeSafeIndex& operator+= | ( | int | i | ) |
Addition assignment operator.
In Debug builds, this method asserts that the resulting index is non-negative.
TypeSafeIndex<Tag>& operator+= | ( | const TypeSafeIndex< Tag > & | other | ) |
Allow addition for indices with the same tag.
|
delete |
Prevent addition for indices of different tags.
const TypeSafeIndex& operator-- | ( | ) |
Prefix decrement operator.
In Debug builds, this method asserts that the resulting index is non-negative.
TypeSafeIndex operator-- | ( | int | ) |
Postfix decrement operator.
In Debug builds, this method asserts that the resulting index is non-negative.
TypeSafeIndex& operator-= | ( | int | i | ) |
Subtraction assignment operator.
In Debug builds, this method asserts that the resulting index is non-negative.
TypeSafeIndex<Tag>& operator-= | ( | const TypeSafeIndex< Tag > & | other | ) |
Allow subtraction for indices with the same tag.
|
delete |
Prevent subtraction for indices of different tags.
bool operator< | ( | const TypeSafeIndex< Tag > & | other | ) | const |
Allow less than test with indices of this tag.
std::enable_if_t<std::is_integral_v<U> && std::is_unsigned_v<U>, bool> operator< | ( | const U & | value | ) | const |
Allow less than test with unsigned integers.
|
delete |
Prevent less than test with indices of other tags.
std::enable_if_t<std::is_integral_v<U> && std::is_unsigned_v<U>, bool> operator<= | ( | const U & | value | ) | const |
Allow less than or equals test with unsigned integers.
bool operator<= | ( | const TypeSafeIndex< Tag > & | other | ) | const |
Allow less than or equals test with indices of this tag.
|
delete |
Prevent less than or equals test with indices of other tags.
|
default |
|
noexcept |
bool operator== | ( | const TypeSafeIndex< Tag > & | other | ) | const |
Allow equality test with indices of this tag.
std::enable_if_t<std::is_integral_v<U> && std::is_unsigned_v<U>, bool> operator== | ( | const U & | value | ) | const |
Allow equality test with unsigned integers.
|
delete |
Prevent equality tests with indices of other tags.
bool operator> | ( | const TypeSafeIndex< Tag > & | other | ) | const |
Allow greater than test with indices of this tag.
std::enable_if_t<std::is_integral_v<U> && std::is_unsigned_v<U>, bool> operator> | ( | const U & | value | ) | const |
Allow greater than test with unsigned integers.
|
delete |
Prevent greater than test with indices of other tags.
bool operator>= | ( | const TypeSafeIndex< Tag > & | other | ) | const |
Allow greater than or equals test with indices of this tag.
std::enable_if_t<std::is_integral_v<U> && std::is_unsigned_v<U>, bool> operator>= | ( | const U & | value | ) | const |
Allow greater than or equals test with unsigned integers.
|
delete |
Prevent greater than or equals test with indices of other tags.
|
friend |
Implements the hash_append generic hashing concept.
And invalid index will successfully hash (in order to satisfy STL requirements), and it is up to the user to confirm it is valid before using it as a key (or other hashing application).