C++ enum class std::size_t implicit conversion
Asked Answered
I

3

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);
Iiette answered 11/8, 2017 at 10:20 Comment(3)
There is no implicit conversion from an enum class to anything, you could create your own get.Whiggism
Okay, but what's the purpose of the : std::size_t syntax than?Iiette
@Iiette to specify the underlying_typeChubby
F
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);
Flagstone answered 11/8, 2017 at 10:24 Comment(2)
You need to add constexpr to get_idx to make this work.Whiggism
@Whiggism yep, exactly!Gaby
W
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);
Whiggism answered 11/8, 2017 at 10:26 Comment(3)
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.Aggie
@Aggie 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.Whiggism
@Holt, thanks, I had somehow not noticed that underlying type can be specified for standard enums.Protactinium
G
2

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;
}    
Gasometry answered 14/2, 2021 at 10:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.