8

P0138R2 proposal begins with1

There is an incredibly useful technique for introducing a new integer type that is almost an exact copy, yet distinct type in modern C++11 programs: an enum class with an explicitly specified underlying type. Example:

enum class Index : int { };    // Note: no enumerator.

One can use Index as a new distinct integer type, it has no implicit conversion to anything (good!).

To convert Index to its underlying type it is useful to define

int operator*(Index index) {
    return static_cast<int>(index);
}

Another way to create Index type is to use old class:

class Index final {
public:
     explicit Index(int index = 0) : index_(index) { }

     int operator*() const {
         return index_;
     }

private:  
     int index_;
};

Both seem to be largely equivalent and can be used in the same way:

void bar(Index index) {
    std::cout << *index;
}

bar(Index{1});

int i = 1;
bar(Index{i});

Pro of enum class: the comparison operators are defined automatically, con of enum class: index value for the default constructed enum class can't be specified, it is always zero.

Are there other practical differences between these alternatives?


1 I changed uint32_t to int to avoid #include <cstdint>.
Evg
  • 25,259
  • 5
  • 41
  • 83
  • Shouldn't your `operator*` return a reference? – Rakete1111 Aug 27 '18 at 19:19
  • 1
    @Rakete1111 (Edited) For the class version that could work, for the enum version that's impossible though. –  Aug 27 '18 at 19:23
  • @FrançoisAndrieux The first question has objective answers, assuming "practical" is objectively measurable (or defined widely). – Yakk - Adam Nevraumont Aug 27 '18 at 19:47
  • I removed the second question. – Evg Aug 27 '18 at 19:48
  • `enum class` is the simpler solution, so I would go with it. Why is the default value so important? – zett42 Aug 27 '18 at 20:18
  • @zett42, I do not claim that it is very important, but sometimes it may be useful to have the nonzero default value, e.g. `invalid = -1`. Probably, `enum class` is better in this case also: one has to be explicit and write `Index::invalid` instead of `Index{}`. – Evg Aug 27 '18 at 20:28
  • FWIW in C++20 we will have the spaceship operator which will make writing the comparisons a lot easier. – NathanOliver Aug 27 '18 at 20:49
  • A con: for `enum class`, you cannot define a conversion nor assignment operator. – geza Aug 27 '18 at 21:28

1 Answers1

1

The alternative for strong types I use is a variation on NamedTypes by Jonathan Boccara. He nicely explains all of the details in multiple posts on his blog, see https://www.fluentcpp.com/2016/12/08/strong-types-for-strong-interfaces/

It's slightly more verbose in writing: using Index = NamedType<int, struct IndexTag, Comparable, ImplicitlyConvertibleTo<int>>;

When you construct this, you need to write something like Index{0}, though, the moment you use it as an index, it should automatically convert to the underlying type.

It has several advantages, including the ability to work on any type. The biggest disadvantage is that it's an external library you have to import instead of built-in functionality.

JVApen
  • 11,008
  • 5
  • 31
  • 67