8

I have defined a tuple and its indices by creating an enum class:

/** parameter { key ; value1 ; value1 ; } */
using Parameter = std::tuple<unsigned, unsigned, unsigned>;
enum class ParameterKey : std::size_t {
    KEY = 0,
    VALUE1 = 1,
    VALUE2 = 2
};

Now I would like to get a value from this tuple:

const auto& key = std::get<ParameterKey::KEY>(*parameterPointer);

I thought the implicit conversion from int to std::size_t is ensured by the : std::size_t syntax :

enum class ParameterKey : std::size_t {
    ....
}

but I'm getting this error

error: no matching function for call to ‘get<KEY>(std::tuple<unsigned int, unsigned int, unsigned int>&)’

This works fine, but it's too garrulous:

const auto& key = std::get<static_cast<unsigned>(ParameterKey::KEY)>(*parameterPointer);
sukovanej
  • 658
  • 2
  • 8
  • 18

3 Answers3

5

There is no implicit conversion here. From enum:

There are no implicit conversions from the values of a scoped enumerator to integral types, although static_cast may be used to obtain the numeric value of the enumerator.

So, you have to use static_cast.


There are some workarounds which are based on static_cast. For instance, one might make use of std::underlying_type:

template<typename T>
constexpr auto get_idx(T value)
{
    return static_cast<std::underlying_type_t<T>>(value);
}

And then:

const auto& key = std::get<get_idx(ParameterKey::KEY)>(*parameterPointer);
Edgar Rokjān
  • 17,245
  • 4
  • 40
  • 67
2

The whole purpose of enum class is to not be implicitly convertible to int, so there is no implicit conversion.

You could create your own get version:

template <ParameterKey key, typename Tuple>
decltype(auto) get(Tuple &&tuple) {
    return std::get<static_cast<std::underlying_type_t<ParameterKey>>(key)>(tuple);
}

Then:

const auto& key = get<ParameterKey::KEY>(*parameterPointer);
Holt
  • 36,600
  • 7
  • 92
  • 139
  • 2
    That's not the whole purpose. It also fixes the incorrect scoping of `enum` and allows you to specify the underlying type explicitly. So there's definitely reason to want `enum class` and also want implicit conversion. – Timmmm Oct 24 '18 at 14:12
  • 1
    @Timmmm You can specify underlying type for standard enums. Agree with the scoping of the standard enums, but the purpose of `enum class` is not to fix this, it's just a side-effect of introducing a new features with 20 years of feedback on existing ones. IMO, unless you're working on very low-level code, you don't want implicit conversion for your `enum class`. – Holt Oct 24 '18 at 14:24
  • @Holt, thanks, I had somehow not noticed that underlying type can be specified for standard enums. – Mike C Sep 20 '19 at 15:49
1

You can make the conversion implicit by creating specialization of an array/vector that accepts this specific enum as an argument:

template <typename ElementType, typename EnumType>
class enumerated_array: array<ElementType, static_cast<size_t>(EnumType::size_)>
{
    using ParentType = array<ElementType, static_cast<size_t>(EnumType::size_)>;
public:
    ElementType& operator[](EnumType enumerator)
    {
        return ParentType::operator[](static_cast<size_t>(enumerator));
    }
    const ElementType& operator[](EnumType enumerator) const
    {
        return ParentType::operator[](static_cast<size_t>(enumerator));
    }
};

// --------------------------------
// and that's how you use it:

enum class PixelColor: size_t { Red, Green, Blue, size_ };
enumerated_array<uint8_t, PixelColor> pixel;
// Can't use any other enum class as an index
pixel[PixelColor::Green] = 255;

Also, while this is not a topic of this question, this approach synergies really well with enum iterators:

template <typename T>
class EnumRangeType
{
public:
    class Iterator
    {
    public:
        Iterator(size_t value):
            value_(value)
        { }

        T operator*() const
        {
            return static_cast<T>(value_);
        }

        void operator++()
        {
            ++value_;
        }

        bool operator!=(Iterator another) const
        {
            return value_ != another.value_;
        }

    private:
        size_t value_;
    };

    static Iterator begin()
    {
        return Iterator(0);
    }

    static Iterator end()
    {
        return Iterator(static_cast<size_t>(T::size_));
    }
};
template <typename T> constexpr EnumRangeType<T> enum_range;

// --------------------------------
// and that's how you use it:

void make_monochrome(enumerated_array<uint8_t, PixelColor>& pixel)
{
    unsigned int total_brightness = 0;
    for (auto color: enum_range<PixelColor>)
        total_brightness += pixel[color];

    uint8_t average_brightness = total_brightness/3;
    for (auto color: enum_range<PixelColor>)
        pixel[color] = average_brightness;
}    
Vasily
  • 65
  • 6