Reputation: 6449
Let's take implementation of the std::unique_lock
from the Standard Library:
struct defer_lock_t { explicit defer_lock_t() = default; };
struct try_to_lock_t { explicit try_to_lock_t() = default; };
struct adopt_lock_t { explicit adopt_lock_t() = default; };
inline constexpr defer_lock_t defer_lock {};
inline constexpr try_to_lock_t try_to_lock {};
inline constexpr adopt_lock_t adopt_lock {};
unique_lock (mutex_type& m, defer_lock_t t) noexcept;
unique_lock (mutex_type& m, try_to_lock_t t);
unique_lock (mutex_type& m, adopt_lock_t t);
Is there a reason why one wouldn't/couldn't/shouldn't use enums instead of structs to implement tag dispatching? Such as:
enum defer_lock_t { defer_lock };
enum try_to_lock_t { try_to_lock };
enum adopt_lock_t { adopt_lock };
unique_lock (mutex_type& m, defer_lock_t t) noexcept;
unique_lock (mutex_type& m, try_to_lock_t t);
unique_lock (mutex_type& m, adopt_lock_t t);
The latter is more concise.
The only advantage of using structs that I can think of is inheritance (eg, iterator tags). But in all other cases, why not use enum?
Upvotes: 14
Views: 1194
Reputation: 28803
Another (minor) benefit on top of the reasons listed by Barry is that if the function call is not inlined, the enum tags have state that needs to be passed into the function, whereas struct tags do not. Even if it looks like the enum is empty, unscoped enumerations always have at least one byte of state that can be cast into them, and scoped enumerations always have at least one bit of state. See http://eel.is/c++draft/enum#dcl.enum-7
Given
struct s {};
enum e {};
void a(s);
void b(e);
void c() {
a(s());
}
void d() {
b(e());
}
clang and gcc for 64-bit Linux both generate
c(): # @c()
jmp a(s) # TAILCALL
d(): # @d()
xor edi, edi
jmp b(e) # TAILCALL
But note that on Windows, the calling convention seems to prevent this (MSVC code gen):
$T1 = 8
void c(void) PROC ; c, COMDAT
movzx ecx, BYTE PTR $T1[rsp]
jmp void a(s) ; a
void c(void) ENDP ; c
void d(void) PROC ; d, COMDAT
xor ecx, ecx
jmp void b(e) ; b
void d(void) ENDP ; d
See it live: https://godbolt.org/z/ss7Ke64ca
Upvotes: 4
Reputation: 303097
First, you don't want the tag types to be {}
-constructible, you want to explicitly name them. This doesn't specifically apply to unique_lock
since unique_lock<std::mutex> lk(m, {})
would be ambiguous, but there's a general principle. Tag types are designed such that you have to write std::defer_lock
(or, if you really want,
std::defer_lock_t()
).
Second, you really only want to use the tag types in the specific context in which they're intended to be used. If you make them enum
s, then you bring in all the enum
functionality - like being convertible to integers:
std::make_unique<int>(std::defer_lock); // ok?
// is this like super deferred?
auto x = std::defer_lock * 2;
// what do you get when you multiply six by nine?
std::unique_lock lk(m, static_cast<std::defer_lock_t>(42));
These other expressions make no sense, so it'd be nice to not even have them exist.
Third, the concision of implementing the standard library in the case where it's just a small, fixed number of characters isn't really a big concern. So I wouldn't even see the enum
implementation as a win. There aren't that many tag types in the standard library.
Upvotes: 16