vendredi 27 février 2015

How to implement std::tuple efficiently such that a tuple of empty types is itself an empty type?

I am implementing std::tuple and I want it to be as efficient in both object size and compile time as possible. I am following the suggestions given here and here.


To improve compile-time performance, the implementation does not use recursive inheritance and instead uses multiple inheritance with the tuple_leaf trick. Additionally it uses the empty base class optimization when possible to reduce the size of the type.


To ensure that the empty base class optimization is always applied, my implementation of tuple itself derives from a base class instead of storing the implementation inside a member variable. However, this causes problems with nested tuples because the tuple_leaf technique works through conversion to a base class. Nested tuples cause ambiguity because the same type of tuple_leaf may occur more than once in the derivation chain.


A program illustrating a simplification of the problem is posted below. Is there a simple way to disambiguate the conversion and allow the program to compile and execute without throwing the assert? I have considered detecting the nested tuple case and encoding the multidimensional position of each tuple_leaf within its type somehow, but that seems complex and would probably degrade compile-time performance.



#include <type_traits>
#include <cassert>

template<int i, class T, bool = std::is_empty<T>::value>
struct tuple_leaf
{
tuple_leaf(T x) : val(x) {}

T& get() { return val; }

T val;
};


template<int i, class T>
struct tuple_leaf<i,T,true> : private T
{
tuple_leaf(T x) : T(x) {}

T& get() { return *this; }
};


template<int i, class T1, class T2>
struct type_at
{
using type = T1;
};


template<class T1, class T2>
struct type_at<1,T1,T2>
{
using type = T2;
};


template<class T1, class T2>
struct tuple_base : tuple_leaf<0,T1>, tuple_leaf<1,T2>
{
tuple_base(T1 a, T2 b) : tuple_leaf<0,T1>(a), tuple_leaf<1,T2>(b) {}

template<int i>
tuple_leaf<i,typename type_at<i,T1,T2>::type> get_leaf()
{
// XXX how to disambiguate this conversion?
return *this;
}
};


// XXX deriving from tuple_base rather than
// making tuple_base a member is the root of the issue
template<class T1, class T2>
struct my_tuple : tuple_base<T1,T2>
{
my_tuple(T1 a, T2 b) : tuple_base<T1,T2>(a, b) {}
};

template<int i, class T1, class T2>
typename type_at<i,T1,T2>::type& get(my_tuple<T1,T2>& t)
{
return (t.template get_leaf<i>()).get();
}

template<class T1,class T2>
my_tuple<T1,T2> make_tuple(T1 a, T2 b)
{
return my_tuple<T1,T2>(a,b);
}

struct empty {};

int main()
{
auto tuple = make_tuple(empty(), make_tuple(empty(),empty()));

assert((std::is_empty<decltype(tuple)>::value));
assert(sizeof(tuple) == sizeof(empty));

get<0>(tuple);

return 0;
}


The compiler output:



$ clang-3.5 -std=c++11 repro.cpp
repro.cpp:47:12: error: ambiguous conversion from derived class 'tuple_base<empty, my_tuple<empty, empty> >' to base class 'tuple_leaf<0, empty, true>':
struct tuple_base<struct empty, struct my_tuple<struct empty, struct empty> > -> tuple_leaf<0, struct empty>
struct tuple_base<struct empty, struct my_tuple<struct empty, struct empty> > -> tuple_leaf<1, struct my_tuple<struct empty, struct empty> > -> struct my_tuple<struct empty, struct empty> -> tuple_base<struct empty, struct empty> -> tuple_leaf<0, struct empty>
return *this;
^~~~~
repro.cpp:63:22: note: in instantiation of function template specialization 'tuple_base<empty, my_tuple<empty, empty> >::get_leaf<0>' requested here

return (t.template get_leaf<i>()).get();
^
repro.cpp:80:3: note: in instantiation of function template specialization 'get<0, empty, my_tuple<empty, empty> >' requested here
get<0>(tuple);
^
1 error generated.

Aucun commentaire:

Enregistrer un commentaire