Drake
Identifier< Tag > Class Template Reference

A simple identifier class. More...

#include <drake/geometry/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...
 
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...
 

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...
 

Detailed Description

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

A simple identifier class.

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 in any operation is considered an error. In Debug build, attempts to compare, get the value of, hash, or write an invalid identifier to a stream will cause program failure.

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

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.

Type-safe Index 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, 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 produce 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). These cannot be used as indices.

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

Constructor & Destructor Documentation

Identifier ( const Identifier< Tag > &  )
default
Identifier ( Identifier< Tag > &&  )
default
Identifier ( )
inline

Default constructor; the result is an invalid identifier.

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

Here is the caller graph for this function:

Member Function Documentation

static Identifier get_new_id ( )
inlinestatic

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.

int64_t get_value ( ) const
inline

Extracts the underlying representation from the identifier.

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

Here is the caller graph for this function:

bool is_valid ( ) const
inline

Reports if the id is valid.

Here is the caller graph for this function:

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

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.

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

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

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: