14

I'm using a std::tuple and defined a class enum to somehow "naming" each of the tuple's fields, forgetting about their actual indexes.

So instead of doing this:

std::tuple<A,B> tup;
/* ... */
std::get<0>(tup) = bleh; // was it 0, or 1?

I did this:

enum class Something {
     MY_INDEX_NAME = 0,
     OTHER_INDEX_NAME
};

std::tuple<A,B> tup;
/* ... */
std::get<Something::MY_INDEX_NAME> = 0; // I don't mind the actual index...

The problem is that, as this compiled using gcc 4.5.2, I've now installed the 4.6.1 version, and my project failed to compile. This snippet reproduces the error:

#include <tuple>
#include <iostream>

enum class Bad {
    BAD = 0
};

enum Good {
    GOOD = 0
};

int main() {
    std::tuple<int, int> tup(1, 10);
    std::cout << std::get<0>(tup) << std::endl;
    std::cout << std::get<GOOD>(tup) << std::endl; // It's OK
    std::cout << std::get<Bad::BAD>(tup) << std::endl; // NOT!
}

The error basically says that there's no overload that matches my call to std::get:

test.cpp: In function ‘int main()’:
test.cpp:16:40: error: no matching function for call to ‘get(std::tuple<int, int>&)’
test.cpp:16:40: note: candidates are:
/usr/include/c++/4.6/utility:133:5: note: template<unsigned int _Int, class _Tp1, class _Tp2> typename std::tuple_element<_Int, std::pair<_Tp1, _Tp2> >::type& std::get(std::pair<_Tp1, _Tp2>&)
/usr/include/c++/4.6/utility:138:5: note: template<unsigned int _Int, class _Tp1, class _Tp2> const typename std::tuple_element<_Int, std::pair<_Tp1, _Tp2> >::type& std::get(const std::pair<_Tp1, _Tp2>&)
/usr/include/c++/4.6/tuple:531:5: note: template<unsigned int __i, class ... _Elements> typename std::__add_ref<typename std::tuple_element<__i, std::tuple<_Elements ...> >::type>::type std::get(std::tuple<_Elements ...>&)
/usr/include/c++/4.6/tuple:538:5: note: template<unsigned int __i, class ... _Elements> typename std::__add_c_ref<typename std::tuple_element<__i, std::tuple<_Elements ...> >::type>::type std::get(const std::tuple<_Elements ...>&)

So, is there any way I can use my enum class as a template argument to std::get? Was this something that wasn't ment to compile, and was fixed in gcc 4.6? I could use a simple enum, but I like the scoping properties of enum classes, so I'd prefer to use the latter if it's possible.

3
  • 2
    If you put an unscoped enum into a struct then you get a type that doesn't pollute the namespace it's in and that can be implicitly converted to whatever type std::get needs: struct foo { enum { bar }; }; and then std::get<foo::bar>(baz). (However none of the enumerators can have the same name as the struct they're in.) Commented May 24, 2012 at 22:00
  • Just to say, "namespace" has been added to plain old enums too, so basically, you'll be able to write Good::GOOD if it's somehow why you wanted to use enum class instead of enum. Commented May 26, 2012 at 11:54
  • Tuple was introduced for generic programing, to store unknown types and numbers of items. Having fixed types tuple is pointless. In such case just use old plain struct { A my_index_name; B other_index_name; }; so you do not have to use std::get<x>(tuple). Do not over-engineer this. Since you are introduce this enums this indicates you are trying naming fields in tuple, this implies you should use regular structs. Commented May 8, 2024 at 8:49

7 Answers 7

11

The strongly-typed enums introduced by C++11 cannot be implicitly converted into integral values of type say int, while std::get expects the template argument to be integral type.

You've to use static_cast to convert the enum values:

std::cout <<std::get<static_cast<int>(Bad::BAD)>(tup)<< std::endl; //Ok now!

Or you can choose to convert into underlying integral type as:

//note that it is constexpr function
template <typename T>
constexpr typename std::underlying_type<T>::type integral(T value) 
{
    return static_cast<typename std::underlying_type<T>::type>(value);
}

then use it as:

std::cout <<std::get<integral(Bad::BAD)>(tup)<< std::endl; //Ok now!
Sign up to request clarification or add additional context in comments.

4 Comments

I'll use plain enums, but I like your solution.
Why not rename your integral function to enum_cast?
@rhalbersma: If I rename integral to enum_cast, then the usage will still be enum_cast(Bad::BAD) which doesn't look like a cast, as a cast usually takes the form of enum_cast<T>(Bad::BAD). So the cast-form doesn't fit in this case.
ah, good point, I had forgotten about the explicit template argument. In any case, if I have a class with only a std::tuple member named data_, then I'd combine your solution with @Jonathan Wakely's and use an old-school anonymous enum { name1_, name2_, ..., nameN_ }; and then do something like Ret_type1& name1() { return std::get<name1_>(data_) }. With this underscore convention, even the old enum will not cause name clashes inside the class.
6

I'd like to add another answer because the original poster asked for a way to have a named access to elements of std::tuple through a class enum.

It is possible to have a template argument of the type of a class enum (at least in GCC). This makes it possible to define your own get retrieving the element of a tuple given a class enum value. Below is an implementation casting this value to int, but you could also do something more fancy:

#include <tuple>

enum class names { A = 0, B, C };

template< names n, class... Types >
typename std::tuple_element<static_cast< std::size_t >( n ), std::tuple<Types...> >::type&
    get( std::tuple<Types...>& t )
{
    return std::get< static_cast< std::size_t >( n ), Types... >( t );
}

int main( int, char** )
{
    std::tuple< char, char, char > t( 'a', 'b', 'c' );
    char c = get<names::A>( t );
}

Note that std::get has two more variants (one for const tuple&, one for tuple&&), which can be implemented exactly the same way.

2 Comments

thanks very usefull +1. I was searchin for a way to specialize templates based on enum value and this is a good start.
You can also make your std::get argument generic by using a universal reference and then SFINAE when is_tuple<std::remove_cvref_t<Tuple>>.
3

Yes, this was a bug in GCC 4.5. Scoped enums don't have implicit conversions to integral types.

2 Comments

Meh, then i'll go back to plain old enums.
@JonathanWakely I was thinking of a generic solution that would let you keep the same syntax.
2

A completely different solution would be:

A& my_field(std::tuple<A,B>& t) { return std::get<0>(t); }
A const& my_field(std::tuple<A,B> const& t) { return std::get<0>(t); }

B& my_other_field(std::tuple<A,B>& t) { return std::get<1>(t); }
B const& my_other_field(std::tuple<A,B> const& t) { return std::get<1>(t); }

my_field(t) = blah;
my_other_field(t) = frob;

Comments

0

2018 update:

It's not a problem anymore. I did have a problem when I tried variations of "enum class int", but this now works fine with VS 2017 15.7.2

enum 
{
  WIDTH,
  HEIGHT
};
std::get<HEIGHT>( Remaining ) = std::get<HEIGHT>( Remaining ) - MinHeight;

2 Comments

the question specifically asks how to make this work for enum class
std::get in C++ 17 can handle enum MyType : std::size_t { Width, Height}; It would be nice if std::get<...> would also support enum class.
0

How about redefining the std::get?

namespace smthg {

template<auto EV, class... Types>
requires std::is_enum_v<decltype(EV)>
constexpr auto const& get(std::tuple<Types...> const& t) noexcept
{
    return std::get<std::to_underlying(EV)>(t);
}

template<auto EV, class... Types>
requires std::is_enum_v<decltype(EV)>
constexpr auto& get(std::tuple<Types...>& t) noexcept
{
    return std::get<std::to_underlying(EV)>(t);
}

}

enum class side { LEFT, RIGHT };
std::tuple<int, int> a {5, 3};

assert(smthg::get<side::LEFT>(a) == 5);

The rhs refs have to be redefined too.

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
-1

My solution is to use:

namespace Something{enum class Something {MY_INDEX_NAME = 0,OTHER_INDEX_NAME};};

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.