Drake
Drake C++ Documentation
TypeSafeIndex< Tag > Class Template Reference

Detailed Description

template<class Tag>
class drake::TypeSafeIndex< Tag >

A type-safe non-negative index class.

Note
This is purposely a separate class from Identifier. For more information, see this section.

This class serves as an upgrade to the standard practice of passing ints around as indices. In the common practice, a method that takes indices into multiple collections would have an interface like:

void foo(int bar_index, int thing_index);

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 ints, but provides compile-time checking. The function would now look like:

void foo(BarIndex bar_index, ThingIndex thing_index);

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:

  • Valid index values are explicitly constructed from int values.
  • The index is implicitly convertible to an int (to serve as an index).
  • The index supports increment, decrement, and in-place addition and subtraction to support standard index-like operations.
  • An index cannot be constructed or compared to an index of another type.
  • In general, indices of different types are not interconvertible.
  • Binary integer operators (e.g., +, -, |, *, etc.) always produce 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:

using FooIndex = TypeSafeIndex<class FooTag>;

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:

using AIndex = TypeSafeIndex<class A>;
using BIndex = TypeSafeIndex<class B>;
AIndex a(1);
BIndex b(1);
if (a == 2) { ... } // Ok.
size_t sz = 7;
if (a == sz) { ... } // Ok.
if (a == b) { ... } // <-- Compiler error.
AIndex invalid; // Creates an invalid index.
++invalid; // Runtime error in Debug build.

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:

for (MyIndex a = 0; a < N; ++a) { ... }

This pattern no longer works because it requires implicit conversion of int to TypeSafeIndex. Instead, the following pattern needs to be used:

for (MyIndex a(0); a < N; ++a) { ... }

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:

VectorXd some_vector = ...;
FooIndex foo_index = ...;
some_vector(foo_index) = 0.0; // Fails to compile.
some_vector(int{foo_index}) = 0.0; // Compiles OK.

TODO(#15354) We hope to fix this irregularity in the future.

See also
drake::geometry::Identifier
Template Parameters
TagThe 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
TypeSafeIndexoperator= (const TypeSafeIndex &)=default
 
TypeSafeIndexoperator= (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 TypeSafeIndexoperator++ ()
 Prefix increment operator. More...
 
TypeSafeIndex operator++ (int)
 Postfix increment operator. More...
 
const TypeSafeIndexoperator-- ()
 Prefix decrement operator. More...
 
TypeSafeIndex operator-- (int)
 Postfix decrement operator. More...
 
Compound assignment operators
TypeSafeIndexoperator+= (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...
 
TypeSafeIndexoperator-= (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...
 

Constructor & Destructor Documentation

◆ TypeSafeIndex() [1/5]

Default constructor; the result is an invalid index.

This only exists to serve applications which require a default constructor.

◆ TypeSafeIndex() [2/5]

TypeSafeIndex ( int64_t  index)
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.

◆ TypeSafeIndex() [3/5]

TypeSafeIndex ( const TypeSafeIndex< U > &  idx)
delete

Disallow construction from another index type.

◆ TypeSafeIndex() [4/5]

TypeSafeIndex ( const TypeSafeIndex< Tag > &  )
default

◆ TypeSafeIndex() [5/5]

TypeSafeIndex ( TypeSafeIndex< Tag > &&  other)
noexcept

Member Function Documentation

◆ is_valid()

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()

operator int ( ) const

Implicit conversion-to-int operator.

◆ operator!=() [1/3]

bool operator!= ( const TypeSafeIndex< Tag > &  other) const

Allow inequality test with indices of this tag.

◆ operator!=() [2/3]

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.

◆ operator!=() [3/3]

bool operator!= ( const TypeSafeIndex< U > &  u) const
delete

Prevent inequality test with indices of other tags.

◆ operator++() [1/2]

const TypeSafeIndex& operator++ ( )

Prefix increment operator.

◆ operator++() [2/2]

TypeSafeIndex operator++ ( int  )

Postfix increment operator.

◆ operator+=() [1/3]

TypeSafeIndex& operator+= ( int  i)

Addition assignment operator.

In Debug builds, this method asserts that the resulting index is non-negative.

◆ operator+=() [2/3]

TypeSafeIndex<Tag>& operator+= ( const TypeSafeIndex< Tag > &  other)

Allow addition for indices with the same tag.

◆ operator+=() [3/3]

TypeSafeIndex<U>& operator+= ( const TypeSafeIndex< U > &  u)
delete

Prevent addition for indices of different tags.

◆ operator--() [1/2]

const TypeSafeIndex& operator-- ( )

Prefix decrement operator.

In Debug builds, this method asserts that the resulting index is non-negative.

◆ operator--() [2/2]

TypeSafeIndex operator-- ( int  )

Postfix decrement operator.

In Debug builds, this method asserts that the resulting index is non-negative.

◆ operator-=() [1/3]

TypeSafeIndex& operator-= ( int  i)

Subtraction assignment operator.

In Debug builds, this method asserts that the resulting index is non-negative.

◆ operator-=() [2/3]

TypeSafeIndex<Tag>& operator-= ( const TypeSafeIndex< Tag > &  other)

Allow subtraction for indices with the same tag.

◆ operator-=() [3/3]

TypeSafeIndex<U>& operator-= ( const TypeSafeIndex< U > &  u)
delete

Prevent subtraction for indices of different tags.

◆ operator<() [1/3]

bool operator< ( const TypeSafeIndex< Tag > &  other) const

Allow less than test with indices of this tag.

◆ operator<() [2/3]

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.

◆ operator<() [3/3]

bool operator< ( const TypeSafeIndex< U > &  u) const
delete

Prevent less than test with indices of other tags.

◆ operator<=() [1/3]

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.

◆ operator<=() [2/3]

bool operator<= ( const TypeSafeIndex< Tag > &  other) const

Allow less than or equals test with indices of this tag.

◆ operator<=() [3/3]

bool operator<= ( const TypeSafeIndex< U > &  u) const
delete

Prevent less than or equals test with indices of other tags.

◆ operator=() [1/2]

TypeSafeIndex& operator= ( const TypeSafeIndex< Tag > &  )
default

◆ operator=() [2/2]

TypeSafeIndex& operator= ( TypeSafeIndex< Tag > &&  other)
noexcept

◆ operator==() [1/3]

bool operator== ( const TypeSafeIndex< Tag > &  other) const

Allow equality test with indices of this tag.

◆ operator==() [2/3]

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.

◆ operator==() [3/3]

bool operator== ( const TypeSafeIndex< U > &  u) const
delete

Prevent equality tests with indices of other tags.

◆ operator>() [1/3]

bool operator> ( const TypeSafeIndex< Tag > &  other) const

Allow greater than test with indices of this tag.

◆ operator>() [2/3]

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.

◆ operator>() [3/3]

bool operator> ( const TypeSafeIndex< U > &  u) const
delete

Prevent greater than test with indices of other tags.

◆ operator>=() [1/3]

bool operator>= ( const TypeSafeIndex< Tag > &  other) const

Allow greater than or equals test with indices of this tag.

◆ operator>=() [2/3]

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.

◆ operator>=() [3/3]

bool operator>= ( const TypeSafeIndex< U > &  u) const
delete

Prevent greater than or equals test with indices of other tags.

Friends And Related Function Documentation

◆ hash_append

void hash_append ( HashAlgorithm &  hasher,
const TypeSafeIndex< Tag > &  i 
)
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).


The documentation for this class was generated from the following file: