4

I am increasingly finding scoped enums unwieldy to use. I am trying to write a set of function overloads including a template for scoped enums that sets/initializes a value by reference--something like this:

void set_value(int& val);
void set_value(double& val);
template <typename ENUM> set_value(ENUM& val);

However, I don't quite see how to write the templated version of set_value without introducing multiple temporary values:

template <typename ENUM>
set_value(ENUM& val)
{
  std::underlying_type_t<ENUM> raw_val;
  set_value(raw_val);    // Calls the appropriate "primitive" overload
  val = static_cast<ENUM>(raw_val);
}

I believe the static_cast introduces a second temporary value in addition to raw_val. I suppose it's possible that one or both of these could be optimized away by the compiler, and in any case it shouldn't really make much difference in terms of performance since the set_value call will also generate temporary values (assuming it's not inlined), but this still seems inelegant. What I would like to do would be something like this:

template <typename ENUM>
set_value(ENUM& val)
{
  set_value(static_cast<std::underlying_type_t<ENUM>&>(val));
}

... but this isn't valid (nor is the corresponding code using pointers directly instead of references) because scoped enums aren't related to their underlying primitives via inheritance.

I could use reinterpret_cast, which, from some preliminary testing, appears to work (and I can't think of any reason why it wouldn't work), but that seems to be frowned upon in C++.

Is there a "standard" way to do this?

Kyle Strand
  • 15,941
  • 8
  • 72
  • 167
  • An enum (scoped or not) is just an integer, and integers are practically free to pass around. There is no need to worry about "temporaries". Trust the optimizer. – Matti Virkkunen May 15 '15 at 02:33
  • @MattiVirkkunen I tried to clarify in the question that this isn't really about optimization, since I don't expect a performance difference either way. I just want more elegant code, because expressiveness is important. – Kyle Strand May 15 '15 at 02:34
  • ...also did you mean to assign val to raw_val? – Matti Virkkunen May 15 '15 at 02:37
  • @MattiVirkkunen Where? – Kyle Strand May 15 '15 at 02:37
  • Before calling the other `modify_value()`. You seem to be passing in an uninitialized variable by reference. – Matti Virkkunen May 15 '15 at 02:38
  • @MattiVirkkunen Ah. I didn't think about that because in my particular case it doesn't matter what the initial value of `val` is. For clarity, I'll rename the dummy function `set_value` instead of `modify_value`. – Kyle Strand May 15 '15 at 02:43
  • 2
    Now your code makes even less sense. What's the point of using pass by reference instead of just returning the value here? A plain return would be much more elegant. – Matti Virkkunen May 15 '15 at 02:44
  • @MattiVirkkunen Why? I agree inasmuch as I would probably return by value if I were writing this class from scratch, but I'm trying to conform to the precedent set by the original author of the class in the existing set of overloads, all of which accept their parameters by reference, and I don't see anything particularly _inelegant_ about that. (This is part of a marshalling framework, and I'm guessing the original pass-by-reference semantics were introduced to facilitate unmarshaling buffer class-members by reference.) – Kyle Strand May 15 '15 at 02:49
  • How is this specific to C++11 scoped enumerations? It should apply to C++98 enumerations too. – Potatoswatter May 15 '15 at 02:56
  • @Potatoswatter For one thing, C++98 didn't allow specifying the underlying type of an enumeration, and `underlying_type` didn't exist yet, which makes the "unrelated types" error quite reasonable and potentially unavoidable. Additionally, I had thought that unscoped enums could be directly assigned from values of their underlying types, but that appears not to be the case, so I suppose the question also applies to unscoped enums. – Kyle Strand May 15 '15 at 03:02
  • @KyleStrand C++98 specified the underlying type for you, based on the range of enumerator values, it just didn't allow customization. You could synthesize `underlying_type` with function overloads. Anyway, yes, the problem here is the aliasing rule which doesn't care about scoped-ness. – Potatoswatter May 15 '15 at 03:06

1 Answers1

0

I could use reinterpret_cast, which, from some preliminary testing, appears to work (and I can't think of any reason why it wouldn't work), but that seems to be frowned upon in C++.

Indeed, that reinterpret_cast is undefined behavior by violation of the strict aliasing rule.

Eliminating a single mov instruction (or otherwise, more or less, copying a register's worth of data) is premature micro-optimization. The compiler is likely to be able to take care of it.

If performance is really important, then follow the optimization process: profile, disassemble, understand the compiler's interpretation, and work together with it within the defined rules.

At a glance, you (and the compiler) might have an easier time with functions like T get_value() instead of void set_value(T). The flow of data and initialization make more sense, although type deduction is lost. You can regain the deduction through tag types, if that's really important.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • As noted in the question and in a previous comment, I'm not concerned about performance; I just find the introduction of `raw_value` inelegant. – Kyle Strand May 15 '15 at 03:06
  • 1
    @KyleStrand As noted in this answer, use `get_value` instead. "Out parameters" should be a last resort. – Potatoswatter May 15 '15 at 03:08