Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does std::unique_lock use type tags to differentiate constructors?

Tags:

In C++11, the std::unique_lock constructor is overloaded to accept the type tags defer_lock_t, try_to_lock_t, and adopt_lock_t:

unique_lock( mutex_type& m, std::defer_lock_t t );
unique_lock( mutex_type& m, std::try_to_lock_t t );
unique_lock( mutex_type& m, std::adopt_lock_t t );

These are empty classes (type tags) defined as follows:

struct defer_lock_t { };
struct try_to_lock_t { };
struct adopt_lock_t { };

This allows the user to disambiguate between the three constructors by passing one of the pre-defined instances of these classes:

constexpr std::defer_lock_t defer_lock {};
constexpr std::try_to_lock_t try_to_lock {};
constexpr std::adopt_lock_t adopt_lock {};

I am surprised that this is not implemented as an enum. As far as I can tell, using an enum would:

  • be simpler to implement
  • not change the syntax
  • allow the argument to be changed at runtime (albeit not very useful in this case).
  • (probably) could be inlined by the compiler with no performance hit

Why does the standard library use type tags, instead of an enum, to disambiguate these constructors? Perhaps more importantly, should I also prefer to use type tags in this situation when writing my own C++ code?

like image 446
Michael Koval Avatar asked Aug 12 '15 06:08

Michael Koval


1 Answers

Tag dispatching

It is a technique known as tag dispatching. It allows the appropriate constructor to be called given the behaviour required by the client.

The reason for tags is that the types used for the tags are thus unrelated and will not conflict during overload resolution. Types (and not values as in the case of enums) are used to resolve overloaded functions. In addition, tags can be used to resolve calls that would otherwise have been ambiguous; in this case the tags would typically be based on some type trait(s).

Tag dispatching with templates means that only code that is required to be used given the construction is required to be implemented.

Tag dispatching allows for easier reading code (in my opinion at least) and simpler library code; the constructor won't have a switch statement and the invariants can be established in the initialiser list, based on these arguments, before executing the constructor itself. Sure, your milage may vary but this has been my general experience using tags.

Boost.org has a write up on the tag dispatching technique. It has a history of use that seems to go back at least as far as the SGI STL.

Why use it?

Why does the standard library use type tags, instead of an enum, to disambiguate these constructors?

Types would be more powerful and flexible when used during overload resolution and the possible implementation than enums; bear in mind the enums were originally unscoped and limited in how they could be used (by contrast to the tags).

Additional noteworthy reasons for tags;

  • Compile time decisions can be made over which constructor to use, and not runtime.
  • Disallows more "hacky" code where a integer is cast to the enum type with a value that is not catered for - design decisions would need to be made out to handle this and then code implemented to cater for any resultant exceptions or errors.
  • Keep in mind that the shared_lock and lock_guard also use these tags, but in the case of the lock_guard, only the adopt_lock is used. An enumeration would introduce more potential error conditions.

I think precedence and history also plays a role here. Given the wide spread use in the Standard Library and elsewhere; it is unlikely to change how situations, such as the original example, are implemented in the library.

Perhaps more importantly, should I also prefer to use type tags in this situation when writing my own C++ code?

This is essentially a design decision. Both can and should be used to target the problems they solve. I have used tags to "route" data and types to the correct function; particular when the implementation would be incompatible at compile time and if there are any overload resolutions in play.

The Standard Library std::advance is often given as an example of how tag dispatching can be used to implement and optimise an algorithm based on traits (or characteristics) of the types used (in this case when the iterators are random access iterators).

It is a powerful technique when used appropriately and should not be ignored. If using enums, favour the newer scoped enums over the older unscoped ones.

like image 188
Niall Avatar answered Oct 28 '22 06:10

Niall