1. Introduction
and
seem have a missing piece.
and
can be used with an index or with a type.
There is already a way to get a type from an index (
and
)
but there is no way to get an index from a type. This adds such a mechanism.
2. Revision History
-
r0 Initial version, discussed in the C++ Library Evolution Working Group email list June 27, 2022 through July 13, 2022. This version of the paper was also incorrectly dated 2021-01-18 instead of 2022-01-18.
-
r1 Responded to LEWG email feedback by adding std::tuple_element_index_v, inheriting from std::integral_constant, clarifying what happens with structs and classes that inherit from std::tuple and std::variant, clarifying what happens with const template parameters, adding spec wording, and possible implementation.
3. Motivating Use Case: Migrating C code to use std :: variant
Consider the following C program with a unit test:
#include <assert.h>struct NumberStorage { enum Type { TYPE_INT , TYPE_DOUBLE } type ; union { int i ; double d ; }; }; struct NumberStorage packageInteger ( int i ) { struct NumberStorage packaged ; packaged . type = TYPE_INT ; packaged . i = i ; return packaged ; } int main () { struct NumberStorage i = packageInteger ( 5 ); assert ( i . type == TYPE_INT ); }
In order to migrate it from using a
to using a
one of the cleanest solutions looks something like this:
#include <assert.h>#include <variant>// Dear future developers: VariantIndex and NumberStorage must stay in sync. // If you reorder or add to one, you must do the same to the other. enum class VariantIndex : std :: size_t { Int , Double }; using NumberStorage = std :: variant < int , double > ; NumberStorage packageInteger ( int i ) { return { i }; } int main () { auto i = packageInteger ( 5 ); auto expectedIndex = static_cast < std :: size_t > ( VariantIndex :: Int ); assert ( i . index () == expectedIndex ); }
Instead, using
would make the code look cleaner and be easier to maintain:
#include <assert.h>#include <variant>using NumberStorage = std :: variant < int , double > ; NumberStorage packageInteger ( int i ) { return { i }; } int main () { auto i = packageInteger ( 5 ); auto expectedIndex = std :: variant_alternative_index_v < int , NumberStorage > ; assert ( i . index () == expectedIndex ); }
4. Motivating Use Case: Simple object serialization
Another place where
is useful is when we have an index from a source such as deserialization and we want to decide what type it represents without using any magic numbers:
struct Reset { }; struct Close { }; struct RunCommand { std :: string command ; }; using Action = std :: variant < Reset , Close , RunCommand > ; void serializeAction ( const Action & action , std :: vector < uint8_t >& buffer ) { buffer . push_back ( action . index ()); if ( auto * runCommand = std :: get_if < RunCommand > ( & action )) serializeString ( runCommand -> command , buffer ); } std :: optional < Action > deserializeAction ( std :: span < const uint8_t > source ) { if ( ! source . size ()) return std :: nullopt ; switch ( source [ 0 ]) { case std :: variant_alternative_index_v < Reset , Action >: return Reset { }; case std :: variant_alternative_index_v < Close , Action >: return Close { }; case std :: variant_alternative_index_v < RunCommand , Action >: return RunCommand { deserializeString ( source . subspan ( 1 )) }; } return std :: nullopt ; }
Like the other motivating use case, this could be done with an enum class or
etc., but this is nicer and references the variant instead of requiring parallel metadata. This was the use case that motivated an implementation in WebKit.
5. Motivation For std :: tuple_element_index_v
Feedback from r0 indicated that while adding std::variant_alternative_index_v for variants, it would be symmetric to add std::tuple_element_index_v for tuples.
6. Addition to variant.helper section
template < class T , class Variant > struct variant_alternative_index ;
All specializations of variant_alternative_index meet the Cpp17UnaryTypeTrait requirements (21.3.2) with a base characteristic of integral_constant<size_t, N> for some N.
template < class T , class Variant > struct variant_alternative_index < T , const Variant > ;
Let VAI denote variant_alternative_index<T, Variant> of the cv-unqualified type T. Then each specialization of the template meets the Cpp17UnaryTypeTrait requirements (21.3.2) with a base characteristic of integral_constant<size_t, VAI::value>.
template < class T , class ... Types > struct variant_alternative_index < T , variant < Types ... >> : integral_constant < size_t , N > { };
Mandates: The type T occurs exactly once in Types.
N is the zero-based index of T in Types.
template < class Type , class Variant > inline constexpr size_t variant_alternative_index_v = variant_alternative_index < Type , Variant >:: value ;
7. Addition to tuple.helper section
template < class T , class Tuple > struct tuple_element_index ;
All specializations of tuple_element_index meet the Cpp17UnaryTypeTrait requirements (21.3.2) with a base characteristic of integral_constant<size_t, N> for some N.
template < class T , class Tuple > struct tuple_element_index < T , const Tuple > ;
Let TEI denote tuple_element_index<T, Tuple> of the cv-unqualified type T. Then each specialization of the template meets the Cpp17UnaryTypeTrait requirements (21.3.2) with a base characteristic of integral_constant<size_t, TEI::value>.
template < class T , class ... Types > struct tuple_element_index < T , tuple < Types ... >> : integral_constant < size_t , N > { };
Mandates: The type T occurs exactly once in Types.
N is the zero-based index of T in Types.
template < class Type , class Tuple > inline constexpr size_t tuple_element_index_v = tuple_element_index < Type , Tuple >:: value ;
8. Ill-formed use
Like the versions of
that take a type, the program must be ill formed if the type is not a unique element in the types of the
or
such as in these four cases:
using Example1 = std :: variant < int , double , double > ; auto ambiguous1 = std :: variant_alternative_index_v < double , Example1 > ; using Example2 = std :: variant < int , double > ; auto missing2 = std :: variant_alternative_index_v < float , Example2 > ; using Example3 = std :: tuple < int , double , double > ; auto ambiguous3 = std :: tuple_element_index_v < double , Example3 > ; using Example4 = std :: tuple < int , double > ; auto missing4 = std :: tuple_element_index_v < float , Example4 > ;
If the constness of the searched-for type does not match the constness of the type in the
or
then there should be a compiler error. This is also the case with
.
using Example5 = std :: variant < int , double > ; auto constDoesNotMatch5 = std :: variant_alternative_index_v < const int , Example5 > ; using Example6 = std :: tuple < int , double > ; auto constDoesNotMatch6 = std :: tuple_element_index_v < const int , Example6 > ;
Like
and
there should be a compiler error if
or
are used with classes or structs that are not
s or
s, respectively, including classes or structs that inherit from
or
.
class Example7 : public std :: variant < int , double > { }; auto nonVariantParameter7 = std :: variant_alternative_index_v < int , Example7 > ; auto nonVariantParameter8 = std :: variant_altermative_index_v < int Example4 > ; class Example9 : public std :: tuple < int , double > { }; auto nonTupleParameter9 = std :: tuple_element_index_v < int , Example9 > ; auto nonTupleParameter10 = std :: tuple_element_index_v < int , Example2 > ;
9. Possible implementation
namespace std { namespace detail { template < size_t , class , class > struct alternative_index_helper ; template < size_t index , class Type , class T > struct alternative_index_helper < index , Type , variant < T >> { static constexpr size_t count = is_same_v < Type , T > ; static constexpr size_t value = index ; }; template < size_t index , class Type , class T , class ... Types > struct alternative_index_helper < index , Type , variant < T , Types ... >> { static constexpr size_t count = is_same_v < Type , T > + alternative_index_helper < index + 1 , Type , variant < Types ... >>:: count ; static constexpr size_t value = is_same_v < Type , T > ? index : alternative_index_helper < index + 1 , Type , variant < Types ... >>:: value ; }; template < size_t , class , class > struct tuple_element_helper ; template < size_t index , class Type , class T > struct tuple_element_helper < index , Type , tuple < T >> { static constexpr size_t count = is_same_v < Type , T > ; static constexpr size_t value = index ; }; template < size_t index , class Type , class T , class ... Types > struct tuple_element_helper < index , Type , tuple < T , Types ... >> { static constexpr size_t count = is_same_v < Type , T > + tuple_element_helper < index + 1 , Type , tuple < Types ... >>:: count ; static constexpr size_t value = is_same_v < Type , T > ? index : tuple_element_helper < index + 1 , Type , tuple < Types ... >>:: value ; }; } // namespace detail template < class T , class Variant > struct variant_alternative_index ; template < class T , class Variant > struct variant_alternative_index < T , const Variant > : variant_alternative_index < T , Variant > { }; template < class T , class ... Types > struct variant_alternative_index < T , variant < Types ... >> : integral_constant < size_t , detail :: alternative_index_helper < 0 , T , variant < Types ... >>:: value > { static_assert ( detail :: alternative_index_helper < 0 , T , remove_cv_t < variant < Types ... >>>:: count == 1 ); }; template < class T , class Variant > inline constexpr size_t variant_alternative_index_v = variant_alternative_index < T , Variant >:: value ; template < class T , class Tuple > struct tuple_element_index ; template < class T , class Tuple > struct tuple_element_index < T , const Tuple > : tuple_element_index < T , Tuple > { }; template < class T , class Tuple > struct tuple_element_index : integral_constant < size_t , detail :: tuple_element_helper < 0 , T , remove_const_t < Tuple >>:: value > { static_assert ( detail :: tuple_element_helper < 0 , T , remove_cv_t < Tuple >>:: count == 1 ); }; template < class T , class Tuple > inline constexpr size_t tuple_element_index_v = tuple_element_index < T , Tuple >:: value ; } // namespace std