#ifndef VTR_STRONG_ID_H #define VTR_STRONG_ID_H /* * This header provides the StrongId class, a template which can be used to * create strong Id's which avoid accidental type conversions (generating * compiler errors when they occur). * * Motivation * ========== * It is common to use an Id (typically an integer) to identify and represent a component. * A basic example (poor style): * * size_t count_net_terminals(int net_id); * * Where a plain int is used to represent the net identifier. * Using a plain basic type is poor style since it makes it unclear that the parameter is * an Id. * * A better example is to use a typedef: * * typedef int NetId; * * size_t count_net_teriminals(NetId net_id); * * It is now clear that the parameter is expecting an Id. * * However this approach has some limitations. In particular, typedef's only create type * aliases, and still allow conversions. This is problematic if there are multiple types * of Ids. For example: * * typedef int NetId; * typedef int BlkId; * * size_t count_net_teriminals(NetId net_id); * * BlkId blk_id = 10; * NetId net_id = 42; * * count_net_teriminals(net_id); //OK * count_net_teriminals(blk_id); //Bug: passed a BlkId as a NetId * * Since typdefs are aliases the compiler issues no errors or warnings, and silently passes * the BlkId where a NetId is expected. This results in hard to diagnose bugs. * * We can avoid this issue by using a StrongId: * * struct net_id_tag; //Phantom tag for NetId * struct blk_id_tag; //Phantom tag for BlkId * * typedef StrongId NetId; * typedef StrongId BlkId; * * size_t count_net_teriminals(NetId net_id); * * BlkId blk_id = 10; * NetId net_id = 42; * * count_net_teriminals(net_id); //OK * count_net_teriminals(blk_id); //Compiler Error: NetId expected! * * StrongId is a template which implements the basic features of an Id, but disallows silent conversions * between different types of Ids. It uses another 'tag' type (passed as the first template parameter) * to uniquely identify the type of the Id (preventing conversions between different types of Ids). * * Usage * ===== * * The StrongId template class takes one required and three optional template parameters: * * 1) Tag - the unique type used to identify this type of Ids [Required] * 2) T - the underlying integral id type (default: int) [Optional] * 3) T sentinel - a value representing an invalid Id (default: -1) [Optional] * * If no value is supllied during construction the StrongId is initialized to the invalid/sentinel value. * * Example 1: default definition * * struct net_id_tag; * typedef StrongId NetId; //Internally stores an integer Id, -1 represents invalid * * Example 2: definition with custom underlying type * * struct blk_id_tag; * typedef StrongId BlkId; //Internally stores a size_t Id, -1 represents invalid * * Example 3: definition with custom underlying type and custom sentinel value * * struct pin_id_tag; * typedef StrongId PinId; //Internally stores a size_t Id, 0 represents invalid * * Example 4: Creating Ids * * struct net_id_tag; * typedef StrongId MyId; //Internally stores an integer Id, -1 represents invalid * * MyId my_id; //Defaults to the sentinel value (-1 by default) * MyId my_other_id = 5; //Explicit construction * MyId my_thrid_id(25); //Explicit construction * * Example 5: Comparing Ids * * struct net_id_tag; * typedef StrongId MyId; //Internally stores an integer Id, -1 represents invalid * * MyId my_id; //Defaults to the sentinel value (-1 by default) * MyId my_id_one = 1; * MyId my_id_two = 2; * MyId my_id_also_one = 1; * * my_id_one == my_id_also_one; //True * my_id_one == my_id; //False * my_id_one == my_id_two; //False * my_id_one != my_id_two; //True * * Example 5: Checking for invalid Ids * * struct net_id_tag; * typedef StrongId MyId; //Internally stores an integer Id, -1 represents invalid * * MyId my_id; //Defaults to the sentinel value * MyId my_id_one = 1; * * //Comparison against a constructed invalid id * my_id == MyId::INVALID(); //True * my_id_one == MyId::INVALID(); //False * my_id_one != MyId::INVALID(); //True * * //The Id can also be evaluated in a boolean context against the sentinel value * if(my_id) //False, my_id is invalid * if(!my_id) //True my_id is valid * if(my_id_one) //True my_id_one is valid * * Example 6: Indexing data structures * * struct my_id_tag; * typedef StrongId MyId; //Internally stores an integer Id, -1 represents invalid * * std::vector my_vec = {0, 1, 2, 3, 4, 5}; * * MyId my_id = 2; * * my_vec[size_t(my_id)]; //Access the third element via explicit conversion */ #include //for std::is_integral #include //for std::size_t #include //for std::hash namespace vtr { //Forward declare the class (needed for operator declarations) template class StrongId; //Forward declare the equality/inequality operators // We need to do this before the class definition so the class can // friend them template bool operator==(const StrongId& lhs, const StrongId& rhs); template bool operator!=(const StrongId& lhs, const StrongId& rhs); template bool operator<(const StrongId& lhs, const StrongId& rhs); //Class template definition with default template parameters template class StrongId { static_assert(std::is_integral::value, "T must be integral"); public: //Gets the invalid Id static constexpr StrongId INVALID() { return StrongId(); } //Default to the sentinel value constexpr StrongId() : id_(sentinel) {} //Only allow explict constructions from a raw Id (no automatic conversions) explicit constexpr StrongId(T id): id_(id) {} //Allow some explicit conversion to useful types //Allow explicit conversion to bool (e.g. if(id)) explicit operator bool() const { return *this != INVALID(); } //Allow explicit conversion to size_t (e.g. my_vector[size_t(strong_id)]) explicit operator std::size_t() const { return static_cast(id_); } //To enable hasing Ids friend std::hash>; //To enable comparisions between Ids // Note that since these are templated functions we provide an empty set of template parameters // after the function name (i.e. <>) friend bool operator== <>(const StrongId& lhs, const StrongId& rhs); friend bool operator!= <>(const StrongId& lhs, const StrongId& rhs); friend bool operator< <>(const StrongId& lhs, const StrongId& rhs); private: T id_; }; template bool operator==(const StrongId& lhs, const StrongId& rhs) { return lhs.id_ == rhs.id_; } template bool operator!=(const StrongId& lhs, const StrongId& rhs) { return !(lhs == rhs); } //Needed for std::map-like containers template bool operator<(const StrongId& lhs, const StrongId& rhs) { return lhs.id_ < rhs.id_; } } //namespace vtr //Specialize std::hash for StrongId's (needed for std::unordered_map-like containers) namespace std { template struct hash> { std::size_t operator()(const vtr::StrongId k) const noexcept { return std::hash()(k.id_); //Hash with the underlying type } }; } //namespace std #endif