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

Detailed Description

template<class Tag>
class drake::Identifier< Tag >

A simple identifier class.

Note
This is purposely a separate class from TypeSafeIndex. For more explanatation, see this section.

This class serves as an upgrade to the standard practice of passing ints around as unique identifiers (or, as in this case, int64_ts). In the common practice, a method that takes identifiers to different types of objects would have an interface like:

void foo(int64_t bar_id, int64_t thing_id);

It is possible for a programmer to accidentally switch the two ids in an invocation. This mistake would still be syntactically correct; it will successfully compile but lead to inscrutable run-time errors. This identifier class provides the same speed and efficiency of passing int64_ts, but enforces unique types and limits the valid operations, providing compile-time checking. The function would now look like:

void foo(BarId bar_id, ThingId thing_id)

and the compiler will catch instances where the order is reversed.

The identifier is a stripped down 64-bit int. Each uniquely declared identifier type has the following properties:

  • The identifier's default constructor produces invalid identifiers.
  • Valid identifiers must be constructed via the copy constructor or through Identifier::get_new_id().
  • The identifier is immutable.
  • The identifier can only be tested for equality/inequality with other identifiers of the same type.
  • Identifiers of different types are not interconvertible.
  • The identifier can be queried for its underlying int64_t value.
  • The identifier can be written to an output stream; its underlying int64_t value gets written.
  • Identifiers are not guaranteed to possess meaningful ordering. I.e., identifiers for two objects created sequentially may not have sequential identifier values.
  • Identifiers can only be generated from the static method get_new_id().

While there is the concept of an invalid identifier, this only exists to facilitate use with STL containers that require default constructors. Using an invalid identifier is generally considered to be an error. In Debug build, attempts to compare, get the value of, or write an invalid identifier to a stream will throw an exception.

Functions that query for identifiers should not return invalid identifiers. We prefer the practice of returning std::optional<Identifier> instead.

It is the designed intent of this class, that ids 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.)

The following alias will create a unique identifier type for class Foo:

using FooId = Identifier<class FooTag>;

Examples of valid and invalid operations

The Identifier guarantees that id instances of different types can't be compared or combined. Efforts to do so will cause a compile-time failure. For example:

using AId = Identifier<class ATag>;
using BId = Identifier<class BTag>;
AId a1; // Compiler error; there is no
// default constructor.
AId a2 = AId::get_new_id(); // Ok.
AId a3(a2); // Ok.
AId a4 = AId::get_new_id(); // Ok.
BId b = BId::get_new_id(); // Ok.
if ( a2 == 1 ) { ... } // Compiler error.
if ( a2 == a4 ) { ... } // Ok, evaluates to false.
if ( a2 == a3 ) { ... } // Ok, evaluates to true.
if ( a2 == b ) { ... } // Compiler error.
a4 = a2; // Ok.
a3 = 7; // Compiler error.

TypeSafeIndex vs Identifier

In principle, the identifier is related to the TypeSafeIndex. In some sense, both are "type-safe" ints. They differ in their semantics. We can consider ints, indices, 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 produce other instances of the index (e.g., increment, in-place addition, etc.) They can be compared with int and other indices 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). These cannot be used as indices.

Ultimately, indices 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
TypeSafeIndex
Template Parameters
TagThe name of the tag that uniquely segregates one instantiation from another.

#include <drake/common/identifier.h>

Public Member Functions

 Identifier ()
 Default constructor; the result is an invalid identifier. More...
 
int64_t get_value () const
 Extracts the underlying representation from the identifier. More...
 
bool is_valid () const
 Reports if the id is valid. More...
 
bool operator== (Identifier other) const
 Compares one identifier with another of the same type for equality. More...
 
bool operator!= (Identifier other) const
 Compares one identifier with another of the same type for inequality. More...
 
bool operator< (Identifier other) const
 Compare two identifiers in order to define a total ordering among identifiers. More...
 
bool is_same_as_valid_id (Identifier valid_id) const
 (Internal use only) Compares this possibly-invalid Identifier with one that is known to be valid and returns false if they don't match. More...
 
Implements CopyConstructible, CopyAssignable, MoveConstructible, MoveAssignable
 Identifier (const Identifier &)=default
 
Identifieroperator= (const Identifier &)=default
 
 Identifier (Identifier &&)=default
 
Identifieroperator= (Identifier &&)=default
 

Static Public Member Functions

static Identifier get_new_id ()
 Generates a new identifier for this id type. More...
 

Protected Member Functions

 Identifier (int64_t val)
 

Friends

template<typename HashAlgorithm >
void hash_append (HashAlgorithm &hasher, const Identifier &i) noexcept
 Implements the hash_append generic hashing concept. More...
 
template<typename H >
AbslHashValue (H state, const Identifier &id)
 Implements Abseil's hashing concept. More...
 

Related Functions

(Note that these are not member functions.)

template<typename Tag >
std::ostream & operator<< (std::ostream &out, const Identifier< Tag > &id)
 Streaming output operator. More...
 

Constructor & Destructor Documentation

◆ Identifier() [1/4]

Identifier ( const Identifier< Tag > &  )
default

◆ Identifier() [2/4]

Identifier ( Identifier< Tag > &&  )
default

◆ Identifier() [3/4]

Default constructor; the result is an invalid identifier.

This only exists to satisfy demands of working with various container classes.

◆ Identifier() [4/4]

Identifier ( int64_t  val)
explicitprotected

Member Function Documentation

◆ get_new_id()

static Identifier get_new_id ( )
static

Generates a new identifier for this id type.

This new identifier will be different from all previous identifiers created. This method does not make any guarantees about the values of ids from successive invocations. This method is guaranteed to be thread safe.

◆ get_value()

int64_t get_value ( ) const

Extracts the underlying representation from the identifier.

This is considered invalid for invalid ids and is strictly enforced in Debug builds.

◆ is_same_as_valid_id()

bool is_same_as_valid_id ( Identifier< Tag >  valid_id) const

(Internal use only) Compares this possibly-invalid Identifier with one that is known to be valid and returns false if they don't match.

It is an error if valid_id is not actually valid, and that is strictly enforced in Debug builds. However, it is not an error if this id is invalid; that results in a false return. This method can be faster than testing separately for validity and equality.

◆ is_valid()

bool is_valid ( ) const

Reports if the id is valid.

◆ operator!=()

bool operator!= ( Identifier< Tag >  other) const

Compares one identifier with another of the same type for inequality.

This is considered invalid for invalid ids and is strictly enforced in Debug builds.

◆ operator<()

bool operator< ( Identifier< Tag >  other) const

Compare two identifiers in order to define a total ordering among identifiers.

This makes identifiers compatible with data structures which require total ordering (e.g., std::set).

◆ operator=() [1/2]

Identifier& operator= ( Identifier< Tag > &&  )
default

◆ operator=() [2/2]

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

◆ operator==()

bool operator== ( Identifier< Tag >  other) const

Compares one identifier with another of the same type for equality.

This is considered invalid for invalid ids and is strictly enforced in Debug builds.

Friends And Related Function Documentation

◆ AbslHashValue

H AbslHashValue ( state,
const Identifier< Tag > &  id 
)
friend

Implements Abseil's hashing concept.

See https://abseil.io/docs/cpp/guides/hash.

◆ hash_append

void hash_append ( HashAlgorithm &  hasher,
const Identifier< Tag > &  i 
)
friend

Implements the hash_append generic hashing concept.

And invalid id 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).

◆ operator<<()

std::ostream & operator<< ( std::ostream &  out,
const Identifier< Tag > &  id 
)
related

Streaming output operator.

This is considered invalid for invalid ids and is strictly enforced in Debug builds.


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