Drake
TypeSafeIndex< Tag > Class Template Reference

A type-safe non-negative index class. More...

#include <drake/common/type_safe_index.h>

Public Member Functions

Constructors
 TypeSafeIndex ()
 Default constructor; the result is an invalid index. More...
 
 TypeSafeIndex (int 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
 
TypeSafeIndexoperator= (int idx)
 Assign the index a value from a non-negative int. More...
 
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)
 Whitelist addition for indices with the same tag. More...
 
template<typename U >
TypeSafeIndex< U > & operator+= (const TypeSafeIndex< U > &u)=delete
 Blacklist addition for indices of different tags. More...
 
TypeSafeIndexoperator-= (int i)
 Subtraction assignment operator. More...
 
TypeSafeIndex< Tag > & operator-= (const TypeSafeIndex< Tag > &other)
 Whitelist subtraction for indices with the same tag. More...
 
template<typename U >
TypeSafeIndex< U > & operator-= (const TypeSafeIndex< U > &u)=delete
 Blacklist 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 apply a whitelist/blacklist approach to 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)
 Whitelist equality test with indices of this tag. More...
 
template<typename U >
bool operator== (const TypeSafeIndex< U > &u)=delete
 Blacklist equality tests with indices of other tags. More...
 
bool operator!= (const TypeSafeIndex< Tag > &other)
 Whitelist inequality test with indices of this tag. More...
 
template<typename U >
bool operator!= (const TypeSafeIndex< U > &u)=delete
 Blacklist inequality test with indices of other tags. More...
 
bool operator< (const TypeSafeIndex< Tag > &other)
 Whitelist less than test with indices of this tag. More...
 
template<typename U >
bool operator< (const TypeSafeIndex< U > &u)=delete
 Blacklist less than test with indices of other tags. More...
 
bool operator<= (const TypeSafeIndex< Tag > &other)
 Whitelist less than or equals test with indices of this tag. More...
 
template<typename U >
bool operator<= (const TypeSafeIndex< U > &u)=delete
 Blacklist less than or equals test with indices of other tags. More...
 
bool operator> (const TypeSafeIndex< Tag > &other)
 Whitelist greater than test with indices of this tag. More...
 
template<typename U >
bool operator> (const TypeSafeIndex< U > &u)=delete
 Blacklist greater than test with indices of other tags. More...
 
bool operator>= (const TypeSafeIndex< Tag > &other)
 Whitelist greater than or equals test with indices of this tag. More...
 
template<typename U >
bool operator>= (const TypeSafeIndex< U > &u)=delete
 Blacklist greater than or equals test with indices of other tags. More...
 

Detailed Description

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

A type-safe non-negative index class.

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. Passing indices by const reference should be considered a misuse.

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.

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) { ... }

Type-safe Index vs Identifier

In principle, the TypeSafeIndex is related to the Identifier. In some sense, both are "type-safe `int`s". They differ in their semantics. We can consider ints, indexes, and identifiers as a list of int types with decreasing functionality.

  • The int, obviously, has the full range of C++ ints.
  • The TypeSafeIndex can be implicitly cast to an int, but there are a limited number of operations on the index that produces other instances of the index (e.g., increment, in-place addition, etc.) They can be compared with int and other indexes of the same type. This behavior arises from the intention of having them serve as an index in an ordered set (e.g., std::vector.)
  • The Identifier is the most restricted. They exist solely to serve as a unique identifier. They are immutable when created. Very few operations exist on them (comparison for equality with other identifiers of the same type, hashing, writing to output stream).

Ultimately, indexes can serve as identifiers (within the scope of the object they index into). Although, their mutability could make this a dangerous practice for a public API. Identifiers are more general in that they don't reflect an object's position in memory (hence the inability to transform to or compare with an int). This decouples details of implementation from the idea of the object. Combined with its immutability, it would serve well as a element of a public API.

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.

Constructor & Destructor Documentation

TypeSafeIndex ( )
inline

Default constructor; the result is an invalid index.

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

Here is the caller graph for this function:

TypeSafeIndex ( int  index)
inlineexplicit

Construction from a non-negative int value.

Constructor only promises to enforce non-negativity in Debug build.

TypeSafeIndex ( const TypeSafeIndex< U > &  idx)
delete

Disallow construction from another index type.

TypeSafeIndex ( const TypeSafeIndex< Tag > &  )
default
TypeSafeIndex ( TypeSafeIndex< Tag > &&  other)
inlinenoexcept

Member Function Documentation

bool is_valid ( ) const
inline

Reports if the index is valid–the only operation on an invalid index that doesn't throw an exception in Debug builds.

Here is the caller graph for this function:

operator int ( ) const
inline

Implicit conversion-to-int operator.

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

Whitelist inequality test with indices of this tag.

Here is the caller graph for this function:

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

Blacklist inequality test with indices of other tags.

const TypeSafeIndex& operator++ ( )
inline

Prefix increment operator.

TypeSafeIndex operator++ ( int  )
inline

Postfix increment operator.

TypeSafeIndex& operator+= ( int  i)
inline

Addition assignment operator.

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

Here is the caller graph for this function:

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

Whitelist addition for indices with the same tag.

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

Blacklist addition for indices of different tags.

const TypeSafeIndex& operator-- ( )
inline

Prefix decrement operator.

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

TypeSafeIndex operator-- ( int  )
inline

Postfix decrement operator.

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

TypeSafeIndex& operator-= ( int  i)
inline

Subtraction assignment operator.

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

Here is the caller graph for this function:

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

Whitelist subtraction for indices with the same tag.

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

Blacklist subtraction for indices of different tags.

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

Whitelist less than test with indices of this tag.

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

Blacklist less than test with indices of other tags.

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

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

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

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

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

Here is the caller graph for this function:

TypeSafeIndex& operator= ( TypeSafeIndex< Tag > &&  other)
inlinenoexcept
TypeSafeIndex& operator= ( int  idx)
inline

Assign the index a value from a non-negative int.

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

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

Whitelist equality test with indices of this tag.

Here is the caller graph for this function:

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

Blacklist equality tests with indices of other tags.

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

Whitelist greater than test with indices of this tag.

Here is the caller graph for this function:

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

Blacklist greater than test with indices of other tags.

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

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

Here is the caller graph for this function:

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

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


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