Using enum instead of struct for tag dispatching in C++
Asked Answered
A

2

14

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?

Archivist answered 16/8, 2018 at 16:8 Comment(1)
I suspect it's leveraging the explicit construction. If for nothing else, to improve error messages.Paper
D
16

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

Deutschland answered 16/8, 2018 at 16:19 Comment(1)
std::defer_lock * 2 is a curiosity, though. More unsettling IMO is the prospect of someone writing, by accident, if ( std::defer_lock ) { /* do stuff ...*/ }Hodgkinson
E
4

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

Enaenable answered 13/4, 2021 at 19:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.