Created
May 31, 2011 14:52
-
-
Save yak1ex/1000636 to your computer and use it in GitHub Desktop.
Fusion adapter for variadic std::tuple
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef YAK_FUSION_STD_TUPLE_ADAPTER_HPP | |
#define YAK_FUSION_STD_TUPLE_ADAPTER_HPP | |
#include <boost/config.hpp> | |
#ifdef BOOST_NO_VARIADIC_TEMPLATES | |
#error Need to enable variadic templates | |
#endif | |
#include <cstddef> | |
#include <tuple> | |
#include <boost/mpl/bool.hpp> | |
#include <boost/mpl/int.hpp> | |
#include <boost/fusion/include/tag_of_fwd.hpp> | |
#include <boost/fusion/include/iterator_base.hpp> | |
/////////////////////////////////////////////////////////////////////// | |
// | |
// Workarond | |
// | |
namespace yak { namespace util { | |
// Currently, cv specialization is missing in libstdc++. | |
template<std::size_t, typename> struct tuple_element; | |
template<std::size_t I, typename ... Types> | |
struct tuple_element<I, std::tuple<Types...>> : std::tuple_element<I, std::tuple<Types...>> {}; | |
template<std::size_t I, typename T> | |
struct tuple_element<I, const T> | |
{ | |
typedef typename std::add_const<typename yak::util::tuple_element<I, T>::type>::type type; | |
}; | |
template<std::size_t I, typename T> | |
struct tuple_element<I, volatile T> | |
{ | |
typedef typename std::add_volatile<typename yak::util::tuple_element<I, T>::type>::type type; | |
}; | |
template<std::size_t I, typename T> | |
struct tuple_element<I, const volatile T> | |
{ | |
typedef typename std::add_cv<typename yak::util::tuple_element<I, T>::type>::type type; | |
}; | |
// Currently, cv specialization is missing in libstdc++. | |
// Currently, integral_constant is not used in libstdc++. | |
template<typename> struct tuple_size; | |
template<typename ... Types> | |
struct tuple_size<std::tuple<Types...>> : boost::mpl::int_<sizeof...(Types)> {}; | |
template<typename T> | |
struct tuple_size<const T> : tuple_size<T> {}; | |
template<typename T> | |
struct tuple_size<volatile T> : tuple_size<T> {}; | |
template<typename T> | |
struct tuple_size<const volatile T> : tuple_size<T> {}; | |
namespace detail { | |
template<typename T> | |
struct tuple_access_type | |
{ | |
typedef const T& const_type; | |
typedef volatile T& volatile_type; | |
typedef const volatile T& cv_type; | |
typedef T& non_cv_type; | |
}; | |
template<typename T> | |
struct tuple_access_type<T&> | |
{ | |
typedef T& const_type; | |
typedef T& volatile_type; | |
typedef T& cv_type; | |
typedef T& non_cv_type; | |
}; | |
template<typename T> | |
struct tuple_access_type<T&&> | |
{ | |
typedef T&& const_type; | |
typedef T&& volatile_type; | |
typedef T&& cv_type; | |
typedef T&& non_cv_type; | |
}; | |
} | |
}} | |
/////////////////////////////////////////////////////////////////////// | |
// | |
// Minimum adapter for std::tuple to fusion sequence | |
// | |
namespace boost { namespace fusion { | |
struct random_access_traversal_tag; | |
}} | |
namespace yak { namespace fusion { | |
struct std_tuple_tag; | |
struct std_tuple_iterator_tag; | |
template<typename Struct, int Pos> | |
struct std_tuple_iterator : boost::fusion::iterator_base<yak::fusion::std_tuple_iterator<Struct, Pos>> | |
{ | |
static_assert(Pos >= 0 && Pos <= yak::util::tuple_size<Struct>::value, "invalid iterator range"); | |
typedef Struct struct_type; | |
typedef boost::fusion::random_access_traversal_tag category; | |
typedef std::integral_constant<int, Pos> index; | |
std_tuple_iterator(Struct &str) : struct_(str) {} | |
Struct& struct_; | |
}; | |
}} | |
namespace boost { namespace fusion { | |
namespace traits { | |
template<typename ... Args> | |
struct tag_of<std::tuple<Args...>> | |
{ | |
typedef yak::fusion::std_tuple_tag type; | |
}; | |
template<typename ... Args> | |
struct tag_of<const std::tuple<Args...>> | |
{ | |
typedef yak::fusion::std_tuple_tag type; | |
}; | |
template<typename ... Args> | |
struct tag_of<volatile std::tuple<Args...>> | |
{ | |
typedef yak::fusion::std_tuple_tag type; | |
}; | |
template<typename ... Args> | |
struct tag_of<const volatile std::tuple<Args...>> | |
{ | |
typedef yak::fusion::std_tuple_tag type; | |
}; | |
template<typename Struct, int Pos> | |
struct tag_of<yak::fusion::std_tuple_iterator<Struct, Pos>> | |
{ | |
typedef yak::fusion::std_tuple_iterator_tag type; | |
}; | |
} | |
namespace extension { | |
template<typename> struct value_of_impl; | |
template<> | |
struct value_of_impl<yak::fusion::std_tuple_iterator_tag> | |
{ | |
template<typename Iterator> struct apply; | |
template<typename Struct, int Pos> | |
struct apply<yak::fusion::std_tuple_iterator<Struct, Pos>> | |
{ | |
typedef typename std::remove_cv< | |
typename yak::util::tuple_element<Pos, Struct>::type | |
>::type type; | |
}; | |
}; | |
template<typename> struct deref_impl; | |
template<> | |
struct deref_impl<yak::fusion::std_tuple_iterator_tag> | |
{ | |
template<typename Iterator> | |
struct apply; | |
template<typename Struct, int Pos> | |
struct apply<yak::fusion::std_tuple_iterator<Struct, Pos>> | |
{ | |
typedef typename yak::util::detail::tuple_access_type< | |
typename yak::util::tuple_element<Pos, Struct>::type | |
>::non_cv_type type; | |
static type call(const yak::fusion::std_tuple_iterator<Struct, Pos> &it) | |
{ | |
return std::get<Pos>(it.struct_); | |
} | |
}; | |
template<typename Struct, int Pos> | |
struct apply<const yak::fusion::std_tuple_iterator<Struct, Pos>> | |
{ | |
typedef typename yak::util::detail::tuple_access_type< | |
typename yak::util::tuple_element<Pos, Struct>::type | |
>::const_type type; | |
static type call(const yak::fusion::std_tuple_iterator<Struct, Pos> &it) | |
{ | |
return std::get<Pos>(it.struct_); | |
} | |
}; | |
}; | |
template<typename> struct next_impl; | |
template<> | |
struct next_impl<yak::fusion::std_tuple_iterator_tag> | |
{ | |
template<typename Iterator> | |
struct apply | |
{ | |
typedef typename Iterator::struct_type struct_type; | |
typedef typename Iterator::index index; | |
typedef yak::fusion::std_tuple_iterator<struct_type, index::value + 1> type; | |
static type call(const Iterator &it) { return type(it.struct_); } | |
}; | |
}; | |
template<typename> struct prior_impl; | |
template<> | |
struct prior_impl<yak::fusion::std_tuple_iterator_tag> | |
{ | |
template<typename Iterator> | |
struct apply | |
{ | |
typedef typename Iterator::struct_type struct_type; | |
typedef typename Iterator::index index; | |
typedef yak::fusion::std_tuple_iterator<struct_type, index::value - 1> type; | |
static type call(const Iterator &it) { return type(it.struct_); } | |
}; | |
}; | |
template<typename> struct distance_impl; | |
template<> | |
struct distance_impl<yak::fusion::std_tuple_iterator_tag> | |
{ | |
template<typename Iterator1, typename Iterator2> | |
struct apply | |
{ | |
typedef typename Iterator1::index index1; | |
typedef typename Iterator2::index index2; | |
typedef int type; | |
static type call(const Iterator1&, const Iterator2&) { return index2::value - index1::value; } | |
}; | |
}; | |
template<typename> struct advance_impl; | |
template<> | |
struct advance_impl<yak::fusion::std_tuple_iterator_tag> | |
{ | |
template<typename Iterator, typename N> | |
struct apply | |
{ | |
typedef typename Iterator::struct_type struct_type; | |
typedef typename Iterator::index index; | |
typedef yak::fusion::std_tuple_iterator<struct_type, index::value + N::value> type; | |
static type call(const Iterator &it) { return type(it.struct_); } | |
}; | |
}; | |
template<typename> struct is_sequence_impl; | |
template<> | |
struct is_sequence_impl<yak::fusion::std_tuple_tag> | |
{ | |
template<typename> struct apply : boost::mpl::true_ {}; | |
}; | |
template<typename> struct begin_impl; | |
template<> | |
struct begin_impl<yak::fusion::std_tuple_tag> | |
{ | |
template<typename Sequence> | |
struct apply | |
{ | |
typedef yak::fusion::std_tuple_iterator<Sequence, 0> type; | |
static type call(Sequence &seq) { return type(seq); } | |
}; | |
}; | |
template<typename> struct end_impl; | |
template<> | |
struct end_impl<yak::fusion::std_tuple_tag> | |
{ | |
template<typename Sequence> | |
struct apply | |
{ | |
typedef yak::fusion::std_tuple_iterator<Sequence, yak::util::tuple_size<Sequence>::value> type; | |
static type call(Sequence &seq) { return type(seq); } | |
}; | |
}; | |
template<typename> struct size_impl; | |
template<> | |
struct size_impl<yak::fusion::std_tuple_tag> | |
{ | |
template<typename Sequence> | |
struct apply : yak::util::tuple_size<Sequence> {}; | |
}; | |
template<typename> struct at_impl; | |
template<> | |
struct at_impl<yak::fusion::std_tuple_tag> | |
{ | |
template<typename Sequence, typename N> | |
struct apply | |
{ | |
typedef typename yak::util::detail::tuple_access_type< | |
typename yak::util::tuple_element<N::value, Sequence>::type | |
>::non_cv_type type; | |
static type call(Sequence &seq) { return std::get<N::value>(seq); } | |
}; | |
template<typename Sequence, typename N> | |
struct apply<const Sequence, N> | |
{ | |
typedef typename yak::util::detail::tuple_access_type< | |
typename yak::util::tuple_element<N::value, const Sequence>::type | |
>::const_type type; | |
static type call(const Sequence &seq) { return std::get<N::value>(seq); } | |
}; | |
template<typename Sequence, typename N> | |
struct apply<volatile Sequence, N> | |
{ | |
typedef typename yak::util::detail::tuple_access_type< | |
typename yak::util::tuple_element<N::value, volatile Sequence>::type | |
>::volatile_type type; | |
static type call(volatile Sequence &seq) { return std::get<N::value>(seq); } | |
}; | |
template<typename Sequence, typename N> | |
struct apply<const volatile Sequence, N> | |
{ | |
typedef typename yak::util::detail::tuple_access_type< | |
typename yak::util::tuple_element<N::value, const volatile Sequence>::type | |
>::cv_type type; | |
static type call(const volatile Sequence &seq) { return std::get<N::value>(seq); } | |
}; | |
}; | |
template<typename> struct value_at_impl; | |
template<> | |
struct value_at_impl<yak::fusion::std_tuple_tag> | |
{ | |
template<typename Sequence, typename N> | |
struct apply : yak::util::tuple_element<N::value, Sequence> {}; | |
}; | |
} | |
}} | |
#endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// You need to compile this file without -std=c++0x option | |
#define BOOST_AUTO_TEST_MAIN | |
#include <boost/test/included/unit_test.hpp> | |
#include <boost/test/auto_unit_test.hpp> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <boost/config.hpp> | |
#ifndef BOOST_NO_VARIADIC_TEMPLATES | |
#include "std_tuple_adapter.hpp" | |
#include <boost/fusion/include/sequence.hpp> | |
#include <boost/fusion/include/iterator.hpp> | |
#include <boost/type_traits/is_same.hpp> | |
#include <boost/type_traits/add_const.hpp> | |
#include <boost/type_traits/add_volatile.hpp> | |
#include <boost/type_traits/add_cv.hpp> | |
#include <boost/mpl/assert.hpp> | |
#include <boost/mpl/vector.hpp> | |
#include <boost/mpl/identity.hpp> | |
#include <boost/mpl/transform.hpp> | |
#include <boost/mpl/range_c.hpp> | |
#include <boost/mpl/copy.hpp> | |
#include <boost/test/auto_unit_test.hpp> | |
/////////////////////////////////////////////////////////////////////// | |
BOOST_AUTO_TEST_CASE(test_empty) | |
{ | |
typedef std::tuple<> Seq; | |
Seq seq; | |
// Checking valid expressions | |
// Forward Sequence | |
typedef boost::fusion::result_of::begin<Seq>::type begin_type; | |
begin_type begin = boost::fusion::begin(seq); | |
typedef boost::fusion::result_of::end<Seq>::type end_type; | |
end_type end = boost::fusion::end(seq); | |
boost::fusion::result_of::size<Seq>::type size = boost::fusion::size(seq); | |
boost::fusion::result_of::empty<Seq>::type empty = boost::fusion::empty(seq); | |
// boost::fusion::result_of::front<Seq>::type front = boost::fusion::front(seq); | |
BOOST_CHECK(begin == end); | |
BOOST_CHECK((boost::is_same<boost::fusion::result_of::size<Seq>::type, boost::mpl::int_<0>>::value)); | |
BOOST_CHECK(size == 0); | |
BOOST_CHECK((boost::is_same<boost::fusion::result_of::empty<Seq>::type, boost::mpl::bool_<true>>::value)); | |
BOOST_CHECK(empty == true); | |
// BOOST_CHECK(front == ...); | |
// front = ... | |
// BOOST_CHECK(front == ...); | |
// BOOST_CHECK(boost::fusion::front(seq) == ...); | |
// next | |
BOOST_MPL_ASSERT((boost::fusion::result_of::equal_to<begin_type, end_type>)); | |
// neq | |
// advance_c<> | |
// advance<> | |
// deref | |
// * | |
boost::fusion::result_of::distance<begin_type, end_type>::type distance = boost::fusion::distance(begin, end); | |
BOOST_CHECK(begin == end); | |
BOOST_CHECK(distance == 0); | |
// Bidirectional Sequence | |
// boost::fusion::result_of::back<Seq>::type back = boost::fusion::back(seq); | |
// BOOST_CHECK(back == ...); | |
// back = ... | |
// BOOST_CHECK(back == ...); | |
// BOOST_CHECK(boost::fusion::back(seq) == ...); | |
// prior | |
// advance_c<> with negative | |
// advance<> with negative | |
// Random Access Sequence | |
// boost::fusion::result_of::at<Seq, 0>::type at0 = boost::fusion::at<0>(seq); | |
// BOOST_CHECK(at0 == ...); | |
// at0 = ... | |
// BOOST_CHECK(at0 == ...); | |
// BOOST_CHECK(boost::fusion::at<0>(seq) == ...); | |
} | |
BOOST_AUTO_TEST_CASE(test_scalar1) | |
{ | |
typedef std::tuple<int> Seq; | |
const int init = 5, change = 3; | |
Seq seq(init); | |
// Checking valid expressions | |
// Forward Sequence | |
typedef boost::fusion::result_of::begin<Seq>::type begin_type; | |
begin_type begin = boost::fusion::begin(seq); | |
typedef boost::fusion::result_of::end<Seq>::type end_type; | |
end_type end = boost::fusion::end(seq); | |
boost::fusion::result_of::size<Seq>::type size = boost::fusion::size(seq); | |
boost::fusion::result_of::empty<Seq>::type empty = boost::fusion::empty(seq); | |
boost::fusion::result_of::front<Seq>::type front = boost::fusion::front(seq); | |
BOOST_CHECK(begin != end); | |
BOOST_CHECK((boost::is_same<boost::fusion::result_of::size<Seq>::type, boost::mpl::int_<1>>::value)); | |
BOOST_CHECK(size == 1); | |
BOOST_CHECK((boost::is_same<boost::fusion::result_of::empty<Seq>::type, boost::mpl::bool_<false>>::value)); | |
BOOST_CHECK(empty == false); | |
BOOST_CHECK(front == init); | |
BOOST_CHECK(&front == &std::get<0>(seq)); | |
front = change; | |
BOOST_CHECK(front == change); | |
BOOST_CHECK(boost::fusion::front(seq) == change); | |
front = init; | |
typedef boost::fusion::result_of::next<begin_type>::type next_type; | |
next_type next = boost::fusion::next(begin); | |
BOOST_CHECK(next == end); | |
BOOST_MPL_ASSERT_NOT((boost::fusion::result_of::equal_to<begin_type, end_type>)); | |
BOOST_MPL_ASSERT((boost::fusion::result_of::equal_to<next_type, end_type>)); | |
BOOST_MPL_ASSERT_NOT((boost::fusion::result_of::equal_to<begin_type, next_type>)); | |
BOOST_MPL_ASSERT((boost::is_same< | |
boost::fusion::result_of::advance_c<begin_type, 1>::type, | |
next_type>)); | |
BOOST_CHECK(boost::fusion::advance_c<1>(begin) == next); | |
BOOST_MPL_ASSERT((boost::is_same< | |
boost::fusion::result_of::advance<begin_type, boost::mpl::int_<1>>::type, | |
next_type>)); | |
BOOST_CHECK(boost::fusion::advance<boost::mpl::int_<1>>(begin) == next); | |
BOOST_CHECK(boost::fusion::deref(begin) == init); | |
BOOST_CHECK(&boost::fusion::deref(begin) == &std::get<0>(seq)); | |
BOOST_CHECK(*begin == boost::fusion::deref(begin)); | |
BOOST_CHECK(&*begin == &boost::fusion::deref(begin)); | |
boost::fusion::result_of::distance<begin_type, end_type>::type distance = boost::fusion::distance(begin, end); | |
BOOST_CHECK(distance == 1); | |
BOOST_CHECK(boost::fusion::distance(begin, boost::fusion::next(begin)) == 1); | |
BOOST_CHECK(boost::fusion::distance(begin, boost::fusion::advance_c<1>(begin)) == 1); | |
BOOST_CHECK(boost::fusion::distance(begin, boost::fusion::advance<boost::mpl::int_<1>>(begin)) == 1); | |
// Bidirectional Sequence | |
boost::fusion::result_of::back<Seq>::type back = boost::fusion::back(seq); | |
BOOST_CHECK(back == init); | |
BOOST_CHECK(&back == &std::get<0>(seq)); | |
back = change; | |
BOOST_CHECK(back == change); | |
BOOST_CHECK(boost::fusion::back(seq) == change); | |
back = init; | |
typedef boost::fusion::result_of::prior<end_type>::type prior_type; | |
prior_type prior = boost::fusion::prior(end); | |
BOOST_CHECK(begin == prior); | |
BOOST_MPL_ASSERT((boost::fusion::result_of::equal_to<begin_type, prior_type>)); | |
BOOST_MPL_ASSERT_NOT((boost::fusion::result_of::equal_to<prior_type, end_type>)); | |
BOOST_CHECK(boost::fusion::prior(boost::fusion::next(begin)) == begin); | |
BOOST_CHECK(boost::fusion::next(boost::fusion::prior(end)) == end); | |
BOOST_MPL_ASSERT((boost::is_same< | |
boost::fusion::result_of::advance_c<end_type, -1>::type, | |
prior_type>)); | |
BOOST_CHECK(boost::fusion::advance_c<-1>(end) == prior); | |
BOOST_MPL_ASSERT((boost::is_same< | |
boost::fusion::result_of::advance<end_type, boost::mpl::int_<-1>>::type, | |
prior_type>)); | |
BOOST_CHECK(boost::fusion::advance<boost::mpl::int_<-1>>(end) == prior); | |
boost::fusion::result_of::distance<prior_type, end_type>::type distance2 = boost::fusion::distance(prior, end); | |
BOOST_CHECK(distance2 == 1); | |
BOOST_CHECK(boost::fusion::distance(boost::fusion::prior(end), end) == 1); | |
BOOST_CHECK(boost::fusion::distance(boost::fusion::advance_c<-1>(end), end) == 1); | |
BOOST_CHECK(boost::fusion::distance(boost::fusion::advance<boost::mpl::int_<-1>>(end), end) == 1); | |
// Random Access Sequence | |
boost::fusion::result_of::at_c<Seq, 0>::type at0 = boost::fusion::at_c<0>(seq); | |
BOOST_MPL_ASSERT((boost::is_same<boost::fusion::result_of::at_c<Seq, 0>::type, int&>)); | |
BOOST_MPL_ASSERT((boost::is_same<boost::fusion::result_of::value_at_c<Seq, 0>::type, int>)); | |
BOOST_CHECK(at0 == init); | |
BOOST_CHECK(&at0 == &std::get<0>(seq)); | |
at0 = change; | |
BOOST_CHECK(at0 == change); | |
BOOST_CHECK(boost::fusion::at_c<0>(seq) == change); | |
at0 = init; | |
} | |
typedef boost::mpl::vector< | |
boost::mpl::quote1<boost::mpl::identity>, | |
boost::mpl::quote1<boost::add_const>, | |
boost::mpl::quote1<boost::add_volatile>, | |
boost::mpl::quote1<boost::add_cv> | |
> trans_types; | |
typedef boost::mpl::vector< | |
std::string, | |
const std::string, | |
volatile std::string, | |
const volatile std::string | |
> base_types; | |
struct make_tuple | |
{ | |
template<typename T> | |
struct apply | |
{ | |
typedef std::tuple<T, T*, T&, T&&> type; | |
}; | |
}; | |
typedef boost::mpl::transform< | |
base_types, | |
make_tuple | |
>::type tuple_types; | |
struct inner_fold | |
{ | |
template<typename T1, typename T2> | |
struct apply | |
{ | |
typedef typename boost::mpl::fold< | |
trans_types, | |
T1, | |
boost::mpl::push_back<boost::mpl::_1, boost::mpl::pair<boost::mpl::_2, T2>> | |
>::type type; | |
}; | |
}; | |
typedef boost::mpl::fold< | |
tuple_types, | |
boost::mpl::vector<>, | |
inner_fold | |
>::type target_types; | |
template<typename T> | |
struct tuple_elems | |
{ | |
template<typename Arg> | |
struct apply | |
{ | |
typedef typename yak::util::tuple_element<Arg::value, T>::type type; | |
}; | |
}; | |
template<typename Trans> | |
struct tuple_access_type | |
{ | |
template<typename T> | |
struct apply | |
{ | |
typedef typename std::conditional< | |
std::is_reference<T>::value, | |
T, | |
typename boost::mpl::apply<Trans, T>::type & | |
>::type type; | |
}; | |
}; | |
BOOST_AUTO_TEST_CASE_TEMPLATE(test_type, T, target_types) | |
{ | |
typedef typename T::first trans_type; | |
typedef typename T::second tuple_type; | |
typedef typename boost::mpl::apply<trans_type, tuple_type>::type target_type; | |
typedef boost::mpl::range_c<int, 0, yak::util::tuple_size<target_type>::value> range_c; | |
typedef typename boost::mpl::copy<range_c, boost::mpl::back_inserter<boost::mpl::vector<>>>::type range; | |
typedef typename boost::mpl::transform<range, tuple_elems<tuple_type>>::type raw_arg_types; | |
typedef typename boost::mpl::transform<range, tuple_elems<target_type>>::type adjust_arg_types; | |
typedef typename boost::mpl::transform<raw_arg_types, tuple_access_type<boost::mpl::protect<boost::mpl::bind<trans_type, boost::mpl::_1>>>>::type access_types; | |
typedef typename boost::mpl::transform<range, boost::fusion::result_of::value_at<target_type, boost::mpl::_1>>::type value_at_types; | |
BOOST_MPL_ASSERT((boost::is_same<value_at_types, adjust_arg_types>)); | |
typedef typename boost::mpl::transform<range, boost::fusion::result_of::at<target_type, boost::mpl::_1>>::type at_types; | |
BOOST_MPL_ASSERT((boost::is_same<at_types, access_types>)); | |
typedef typename boost::mpl::transform< | |
range, | |
boost::fusion::result_of::deref< | |
boost::fusion::result_of::advance< | |
typename boost::fusion::result_of::begin<target_type>::type, | |
boost::mpl::_1 | |
> | |
> | |
>::type deref_types; | |
BOOST_MPL_ASSERT((boost::is_same<deref_types, access_types>)); | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment