Program Listing for File tuple.hpp

Return to documentation for file (camp/tuple.hpp)

/*
Copyright (c) 2016-18, Lawrence Livermore National Security, LLC.
Produced at the Lawrence Livermore National Laboratory
Maintained by Tom Scogland <scogland1@llnl.gov>
CODE-756261, All rights reserved.
This file is part of camp.
For details about use and distribution, please read LICENSE and NOTICE from
http://github.com/llnl/camp
*/

#ifndef camp_tuple_HPP__
#define camp_tuple_HPP__

#include <sstream>
#include <type_traits>

#include "camp/concepts.hpp"
#include "camp/map.hpp"

namespace camp
{

template <typename... Rest>
struct tuple;

template <typename TagList, typename... Elements>
class tagged_tuple;

template <template <typename... Ts> class Tup>
using is_tuple = typename std::is_base_of<tuple<>, Tup<>>::type;

template <typename Tuple>
struct tuple_size;

template <camp::idx_t i, typename T>
struct tuple_element {
  using type = camp::at_v<typename T::TList, i>;
};

template <camp::idx_t i, typename T>
using tuple_element_t = typename tuple_element<i, T>::type;

template <typename T, typename Tuple>
using tuple_ebt_t =
    typename tuple_element<camp::at_key<typename Tuple::TMap, T>::value,
                           Tuple>::type;

template <typename... Args>
struct tuple_size<tuple<Args...>> : ::camp::num<sizeof...(Args)> {
};

template <typename L, typename... Args>
struct tuple_size<tagged_tuple<L, Args...>> : ::camp::num<sizeof...(Args)> {
};

template <typename T>
struct tuple_size<const T> : num<tuple_size<T>::value> {
};
template <typename T>
struct tuple_size<volatile T> : num<tuple_size<T>::value> {
};
template <typename T>
struct tuple_size<const volatile T> : num<tuple_size<T>::value> {
};


namespace internal
{

  template <class T>
  struct unwrap_refwrapper {
    using type = T;
  };

  template <class T>
  struct unwrap_refwrapper<std::reference_wrapper<T>> {
    using type = T&;
  };

  template <class T>
  using special_decay_t =
      typename unwrap_refwrapper<typename std::decay<T>::type>::type;
}  // namespace internal

namespace internal
{
  template <camp::idx_t index,
            typename Type,
            bool Empty = std::is_empty<Type>::value>
  struct CAMP_EMPTY_BASES tuple_storage {
    CAMP_HOST_DEVICE constexpr tuple_storage() : val(){};

    CAMP_SUPPRESS_HD_WARN
    template <typename T>
    CAMP_HOST_DEVICE constexpr tuple_storage(T&& v)
        // initializing with (...) instead of {...} for compiler compatability
        // some compilers complain when Type has no members and we use {...} to
        // initialize val
        : val(std::forward<T>(v))
    {
    }

    CAMP_HOST_DEVICE constexpr const Type& get_inner() const& noexcept
    {
      return val;
    }
    CAMP_HOST_DEVICE constexpr const Type&& get_inner() const&& noexcept
    {
      return static_cast<const Type&&>(val);
    }

    CAMP_HOST_DEVICE constexpr Type& get_inner() & noexcept
    {
      return val;
    }
    CAMP_HOST_DEVICE constexpr Type&& get_inner() && noexcept
    {
      return static_cast<Type&&>(val);
    }

  public:
    Type val;
  };
  template <camp::idx_t index, typename Type>
  struct CAMP_EMPTY_BASES tuple_storage<index, Type, true> : private Type {
    CAMP_HOST_DEVICE constexpr tuple_storage() : Type(){};

    static_assert(std::is_empty<Type>::value,
                  "this specialization should only ever be used for empty "
                  "types");

    CAMP_SUPPRESS_HD_WARN
    template <typename T>
    CAMP_HOST_DEVICE constexpr tuple_storage(T&& v) : Type(std::forward<T>(v))
    {
    }

    CAMP_HOST_DEVICE constexpr const Type& get_inner() const& noexcept
    {
      return ((Type const*)this)[0];
    }
    CAMP_HOST_DEVICE constexpr const Type&& get_inner() const&& noexcept
    {
      return static_cast<const Type&&>(((Type const*)this)[0]);
    }

    CAMP_HOST_DEVICE constexpr Type& get_inner() & noexcept
    {
      return ((Type*)this)[0];
    }
    CAMP_HOST_DEVICE constexpr Type&& get_inner() && noexcept
    {
      return static_cast<Type&&>(((Type*)this)[0]);
    }
  };

  template <typename T, camp::idx_t I>
  using tpl_get_store = internal::tuple_storage<I, tuple_element_t<I, T>>;
}  // namespace internal

// by index
template <camp::idx_t index, class... Types>
CAMP_HOST_DEVICE constexpr
tuple_element_t<index, tuple<Types...>> const&  get(const tuple<Types...>&  t) noexcept;
template <camp::idx_t index, class... Types>
CAMP_HOST_DEVICE constexpr
tuple_element_t<index, tuple<Types...>> const&& get(const tuple<Types...>&& t) noexcept;
template <camp::idx_t index, class... Types>
CAMP_HOST_DEVICE constexpr
tuple_element_t<index, tuple<Types...>>      &  get(      tuple<Types...>&  t) noexcept;
template <camp::idx_t index, class... Types>
CAMP_HOST_DEVICE constexpr
tuple_element_t<index, tuple<Types...>>      && get(      tuple<Types...>&& t) noexcept;

// by type
template <typename T, class... Types>
CAMP_HOST_DEVICE constexpr
tuple_ebt_t<T, tuple<Types...>> const&  get(const tuple<Types...>&  t) noexcept;
template <typename T, class... Types>
CAMP_HOST_DEVICE constexpr
tuple_ebt_t<T, tuple<Types...>> const&& get(const tuple<Types...>&& t) noexcept;
template <typename T, class... Types>
CAMP_HOST_DEVICE constexpr
tuple_ebt_t<T, tuple<Types...>>      &  get(      tuple<Types...>&  t) noexcept;
template <typename T, class... Types>
CAMP_HOST_DEVICE constexpr
tuple_ebt_t<T, tuple<Types...>>      && get(      tuple<Types...>&& t) noexcept;

// tagged_tuple by type
template <typename T, typename TagList, class... Types>
CAMP_HOST_DEVICE constexpr
tuple_ebt_t<T, tagged_tuple<TagList, Types...>> const&  get(const tagged_tuple<TagList, Types...>&  t) noexcept;
template <typename T, typename TagList, class... Types>
CAMP_HOST_DEVICE constexpr
tuple_ebt_t<T, tagged_tuple<TagList, Types...>> const&& get(const tagged_tuple<TagList, Types...>&& t) noexcept;
template <typename T, typename TagList, class... Types>
CAMP_HOST_DEVICE constexpr
tuple_ebt_t<T, tagged_tuple<TagList, Types...>>      &  get(      tagged_tuple<TagList, Types...>&  t) noexcept;
template <typename T, typename TagList, class... Types>
CAMP_HOST_DEVICE constexpr
tuple_ebt_t<T, tagged_tuple<TagList, Types...>>      && get(      tagged_tuple<TagList, Types...>&& t) noexcept;

namespace internal
{

  template <typename Indices, typename Typelist>
  struct tuple_helper;

  class expand_tag
  {
  };

  template <typename... Types, camp::idx_t... Indices>
  struct CAMP_EMPTY_BASES
      tuple_helper<camp::idx_seq<Indices...>, camp::list<Types...>>
      : public internal::tuple_storage<Indices, Types>... {

    tuple_helper& operator=(const tuple_helper& rhs) = default;
    constexpr tuple_helper() = default;
    constexpr tuple_helper(tuple_helper const&) = default;
    constexpr tuple_helper(tuple_helper&&) = default;

    template <typename... Args>
    CAMP_HOST_DEVICE constexpr tuple_helper(Args&&... args)
        : tuple_storage<Indices, Types>(std::forward<Args>(args))...
    {
    }

    template <typename T>
    CAMP_HOST_DEVICE constexpr explicit tuple_helper(expand_tag, T&& rhs)
        : tuple_helper(get<Indices>(rhs)...)
    {
    }

    template <typename T>
    CAMP_HOST_DEVICE constexpr explicit tuple_helper(expand_tag, const T& rhs)
        : tuple_helper(get<Indices>(rhs)...)
    {
    }

    template <typename RTuple>
    CAMP_HOST_DEVICE tuple_helper& operator=(const RTuple& rhs)
    {
      return (camp::sink((this->tuple_storage<Indices, Types>::get_inner() =
                              ::camp::get<Indices>(rhs))...),
              *this);
    }
  };

  template <typename Types, typename Indices>
  struct tag_map;
  template <typename... Types, camp::idx_t... Indices>
  struct tag_map<camp::list<Types...>, camp::idx_seq<Indices...>> {
    using type = camp::list<camp::list<Types, camp::num<Indices>>...>;
  };

}  // namespace internal

template <typename... Elements>
struct tuple {
private:
  using Self = tuple;
  using Base = internal::tuple_helper<camp::make_idx_seq_t<sizeof...(Elements)>,
                                      camp::list<Elements...>>;

  template <typename... Ts>
  struct is_pack_this_tuple : false_type {
  };
  template <typename That>
  struct is_pack_this_tuple<That> : std::is_same<tuple, decay<That>> {
  };

public:
  using TList = camp::list<Elements...>;
  using TMap = typename internal::tag_map<
      camp::list<Elements...>,
      camp::make_idx_seq_t<sizeof...(Elements)>>::type;
  using type = tuple;
  Base base;  // Place this back into private when XLC can handle this better.

private:

  template <camp::idx_t index, class... Types>
  CAMP_HOST_DEVICE constexpr friend tuple_element_t<index, tuple<Types...>> const&
  get(const tuple<Types...>&  t) noexcept;
  template <camp::idx_t index, class... Types>
  CAMP_HOST_DEVICE constexpr friend tuple_element_t<index, tuple<Types...>> const&&
  get(const tuple<Types...>&& t) noexcept;
  template <camp::idx_t index, class... Types>
  CAMP_HOST_DEVICE constexpr friend tuple_element_t<index, tuple<Types...>> &
  get(      tuple<Types...>&  t) noexcept;
  template <camp::idx_t index, class... Types>
  CAMP_HOST_DEVICE constexpr friend tuple_element_t<index, tuple<Types...>> &&
  get(      tuple<Types...>&& t) noexcept;

  template <typename T, class... Types>
  CAMP_HOST_DEVICE constexpr friend tuple_ebt_t<T, tuple<Types...>> const&
  get(const tuple<Types...>&  t) noexcept;
  template <typename T, class... Types>
  CAMP_HOST_DEVICE constexpr friend tuple_ebt_t<T, tuple<Types...>> const&&
  get(const tuple<Types...>&& t) noexcept;
  template <typename T, class... Types>
  CAMP_HOST_DEVICE constexpr friend tuple_ebt_t<T, tuple<Types...>> &
  get(      tuple<Types...>&  t) noexcept;
  template <typename T, class... Types>
  CAMP_HOST_DEVICE constexpr friend tuple_ebt_t<T, tuple<Types...>> &&
  get(      tuple<Types...>&& t) noexcept;

public:
  CAMP_HOST_DEVICE constexpr explicit tuple(const Elements&... rest)
      : base{rest...}
  {
  }

  template <typename... Args,
            typename std::enable_if<
                !is_pack_this_tuple<Args...>::value>::type* = nullptr>
  CAMP_HOST_DEVICE constexpr explicit tuple(Args&&... rest)
      : base{std::forward<Args>(rest)...}
  {
  }

  template <typename... RTypes,
            typename std::enable_if<
                sizeof...(RTypes) == sizeof...(Elements)>::type* = nullptr>
  CAMP_HOST_DEVICE constexpr explicit tuple(const tuple<RTypes...>& rhs)
      : base(internal::expand_tag{}, rhs)
  {
  }

  template <typename... RTypes,
            typename std::enable_if<
                sizeof...(RTypes) == sizeof...(Elements)>::type* = nullptr>
  CAMP_HOST_DEVICE constexpr explicit tuple(tuple<RTypes...>&& rhs)
      : base(internal::expand_tag{}, rhs)
  {
  }

  template <typename... RTypes>
  CAMP_HOST_DEVICE constexpr Self& operator=(const tuple<RTypes...>& rhs)
  {
    base.operator=(rhs);
    return *this;
  }
};

template <typename TagList, typename... Elements>
class tagged_tuple : public tuple<Elements...>
{
  using Self = tagged_tuple;
  using Base = tuple<Elements...>;

public:
  using TMap = typename internal::
      tag_map<TagList, camp::make_idx_seq_t<sizeof...(Elements)>>::type;
  using type = tagged_tuple;
  using Base::Base;

private:
  template <typename T, typename TagList_, class... Types>
  CAMP_HOST_DEVICE constexpr friend tuple_ebt_t<T, tagged_tuple<TagList_, Types...>> const&
  get(const tagged_tuple<TagList_, Types...>&  t) noexcept;
  template <typename T, typename TagList_, class... Types>
  CAMP_HOST_DEVICE constexpr friend tuple_ebt_t<T, tagged_tuple<TagList_, Types...>> const&&
  get(const tagged_tuple<TagList_, Types...>&& t) noexcept;
  template <typename T, typename TagList_, class... Types>
  CAMP_HOST_DEVICE constexpr friend tuple_ebt_t<T, tagged_tuple<TagList_, Types...>> &
  get(      tagged_tuple<TagList_, Types...>&  t) noexcept;
  template <typename T, typename TagList_, class... Types>
  CAMP_HOST_DEVICE constexpr friend tuple_ebt_t<T, tagged_tuple<TagList_, Types...>> &&
  get(      tagged_tuple<TagList_, Types...>&& t) noexcept;

public:
  constexpr tagged_tuple() = default;

  constexpr tagged_tuple(tagged_tuple const& o) = default;
  constexpr tagged_tuple(tagged_tuple&& o) = default;

  tagged_tuple& operator=(tagged_tuple const& rhs) = default;
  tagged_tuple& operator=(tagged_tuple&& rhs) = default;

  CAMP_HOST_DEVICE constexpr explicit tagged_tuple(const Base& rhs) : Base{rhs}
  {
  }

  template <typename... RTypes>
  CAMP_HOST_DEVICE constexpr explicit tagged_tuple(
      const tagged_tuple<RTypes...>& rhs)
      : Base(rhs)
  {
  }

  template <typename... RTypes>
  CAMP_HOST_DEVICE constexpr explicit tagged_tuple(
      tagged_tuple<RTypes...>&& rhs)
      : Base(rhs)
  {
  }

  using Base::operator=;
  template <typename... RTypes>
  CAMP_HOST_DEVICE constexpr Self& operator=(const tagged_tuple<RTypes...>& rhs)
  {
    Base::operator=(rhs);
    return *this;
  }
};

template <>
struct tuple<>
{
public:
  using TList = camp::list<>;
  using TMap = TList;
  using type = tuple;
};

#if defined(__cplusplus) && __cplusplus >= 201703L
template <class... T>
tuple(T...) -> tuple<T...>;
#endif

template <typename... Tags, typename... Args>
struct as_list_s<tagged_tuple<camp::list<Tags...>, Args...>> {
  using type = list<Args...>;
};


// by index
template <camp::idx_t index, class... Types>
CAMP_HOST_DEVICE constexpr tuple_element_t<index, tuple<Types...>> const&
get(const tuple<Types...>&  t) noexcept
{
  using internal::tpl_get_store;
  static_assert(tuple_size<tuple<Types...>>::value > index, "index out of range");
  return static_cast<const tpl_get_store<tuple<Types...>, index>&>(t.base).get_inner();
}

template <camp::idx_t index, class... Types>
CAMP_HOST_DEVICE constexpr tuple_element_t<index, tuple<Types...>> const&&
get(const tuple<Types...>&& t) noexcept
{
  using internal::tpl_get_store;
  static_assert(tuple_size<tuple<Types...>>::value > index, "index out of range");
  return static_cast<const tpl_get_store<tuple<Types...>, index>&&>(t.base).get_inner();
}

template <camp::idx_t index, class... Types>
CAMP_HOST_DEVICE constexpr tuple_element_t<index, tuple<Types...>> &
get(      tuple<Types...>&  t) noexcept
{
  using internal::tpl_get_store;
  static_assert(tuple_size<tuple<Types...>>::value > index, "index out of range");
  return static_cast<tpl_get_store<tuple<Types...>, index>&>(t.base).get_inner();
}

template <camp::idx_t index, class... Types>
CAMP_HOST_DEVICE constexpr tuple_element_t<index, tuple<Types...>> &&
get(      tuple<Types...>&& t) noexcept
{
  using internal::tpl_get_store;
  static_assert(tuple_size<tuple<Types...>>::value > index, "index out of range");
  return static_cast<tpl_get_store<tuple<Types...>, index>&&>(t.base).get_inner();
}

// by type
template <typename T, class... Types>
CAMP_HOST_DEVICE constexpr tuple_ebt_t<T, tuple<Types...>> const&
get(const tuple<Types...>&  t) noexcept
{
  using internal::tpl_get_store;
  using index_type = camp::at_key<typename tuple<Types...>::TMap, T>;
  static_assert(!std::is_same<camp::nil, index_type>::value,
                "invalid type index");

  return static_cast<const tpl_get_store<tuple<Types...>, index_type::value>&>(t.base)
      .get_inner();
}

template <typename T, class... Types>
CAMP_HOST_DEVICE constexpr tuple_ebt_t<T, tuple<Types...>> const&&
get(const tuple<Types...>&& t) noexcept
{
  using internal::tpl_get_store;
  using index_type = camp::at_key<typename tuple<Types...>::TMap, T>;
  static_assert(!std::is_same<camp::nil, index_type>::value,
                "invalid type index");

  return static_cast<const tpl_get_store<tuple<Types...>, index_type::value>&&>(t.base)
      .get_inner();
}

template <typename T, class... Types>
CAMP_HOST_DEVICE constexpr tuple_ebt_t<T, tuple<Types...>> &
get(      tuple<Types...>&  t) noexcept
{
  using internal::tpl_get_store;
  using index_type = camp::at_key<typename tuple<Types...>::TMap, T>;
  static_assert(!std::is_same<camp::nil, index_type>::value,
                "invalid type index");

  return static_cast<tpl_get_store<tuple<Types...>, index_type::value>&>(t.base)
      .get_inner();
}

template <typename T, class... Types>
CAMP_HOST_DEVICE constexpr tuple_ebt_t<T, tuple<Types...>> &&
get(      tuple<Types...>&& t) noexcept
{
  using internal::tpl_get_store;
  using index_type = camp::at_key<typename tuple<Types...>::TMap, T>;
  static_assert(!std::is_same<camp::nil, index_type>::value,
                "invalid type index");

  return static_cast<tpl_get_store<tuple<Types...>, index_type::value>&&>(t.base)
      .get_inner();
}

// tagged_tuple by type
template <typename T, typename TagList, class... Types>
CAMP_HOST_DEVICE constexpr tuple_ebt_t<T, tagged_tuple<TagList, Types...>> const&
get(const tagged_tuple<TagList, Types...>&  t) noexcept
{
  using internal::tpl_get_store;
  using index_type = camp::at_key<typename tagged_tuple<TagList, Types...>::TMap, T>;
  static_assert(!std::is_same<camp::nil, index_type>::value,
                "invalid type index");

  return static_cast<const tpl_get_store<tagged_tuple<TagList, Types...>, index_type::value>&>(t.base)
      .get_inner();
}

template <typename T, typename TagList, class... Types>
CAMP_HOST_DEVICE constexpr tuple_ebt_t<T, tagged_tuple<TagList, Types...>> const&&
get(const tagged_tuple<TagList, Types...>&& t) noexcept
{
  using internal::tpl_get_store;
  using index_type = camp::at_key<typename tagged_tuple<TagList, Types...>::TMap, T>;
  static_assert(!std::is_same<camp::nil, index_type>::value,
                "invalid type index");

  return static_cast<const tpl_get_store<tagged_tuple<TagList, Types...>, index_type::value>&&>(t.base)
      .get_inner();
}

template <typename T, typename TagList, class... Types>
CAMP_HOST_DEVICE constexpr tuple_ebt_t<T, tagged_tuple<TagList, Types...>> &
get(      tagged_tuple<TagList, Types...>&  t) noexcept
{
  using internal::tpl_get_store;
  using index_type = camp::at_key<typename tagged_tuple<TagList, Types...>::TMap, T>;
  static_assert(!std::is_same<camp::nil, index_type>::value,
                "invalid type index");

  return static_cast<tpl_get_store<tagged_tuple<TagList, Types...>, index_type::value>&>(t.base)
      .get_inner();
}

template <typename T, typename TagList, class... Types>
CAMP_HOST_DEVICE constexpr tuple_ebt_t<T, tagged_tuple<TagList, Types...>> &&
get(      tagged_tuple<TagList, Types...>&& t) noexcept
{
  using internal::tpl_get_store;
  using index_type = camp::at_key<typename tagged_tuple<TagList, Types...>::TMap, T>;
  static_assert(!std::is_same<camp::nil, index_type>::value,
                "invalid type index");

  return static_cast<tpl_get_store<tagged_tuple<TagList, Types...>, index_type::value>&&>(t.base)
      .get_inner();
}


template <typename... Args>
CAMP_HOST_DEVICE constexpr auto make_tuple(Args&&... args)
{
  return tuple<internal::special_decay_t<Args>...>{std::forward<Args>(args)...};
}

template <typename TagList, typename... Args>
CAMP_HOST_DEVICE constexpr auto make_tagged_tuple(Args&&... args)
{
  return tagged_tuple<TagList, internal::special_decay_t<Args>...>{
      std::forward<Args>(args)...};
}

template <typename... Args>
CAMP_HOST_DEVICE constexpr auto forward_as_tuple(Args&&... args) noexcept
{
  return tuple<Args&&...>(std::forward<Args>(args)...);
}

template <class... Types>
CAMP_HOST_DEVICE constexpr tuple<Types&...> tie(Types&... args) noexcept
{
  return tuple<Types&...>{args...};
}

template <typename L,
          typename R,
          camp::idx_t... Lidx,
          camp::idx_t... Ridx>
CAMP_HOST_DEVICE constexpr auto tuple_cat_pair(L&& l,
                                               camp::idx_seq<Lidx...>,
                                               R&& r,
                                               camp::idx_seq<Ridx...>) noexcept
{
  return ::camp::tuple<camp::at_v<typename std::decay_t<L>::TList, Lidx>...,
                       camp::at_v<typename std::decay_t<R>::TList, Ridx>...>(
      ::camp::get<Lidx>(std::forward<L>(l))...,
      ::camp::get<Ridx>(std::forward<R>(r))...);
}

template <typename L, typename R>
CAMP_HOST_DEVICE constexpr auto tuple_cat_pair(L&& l, R&& r) noexcept
{
  return tuple_cat_pair(std::forward<L>(l),
                        camp::idx_seq_from_t<L>{},
                        std::forward<R>(r),
                        camp::idx_seq_from_t<R>{});
}

CAMP_SUPPRESS_HD_WARN
template <typename Fn, camp::idx_t... Sequence, typename TupleLike>
CAMP_HOST_DEVICE constexpr auto invoke_with_order(TupleLike&& tup,
                                                  Fn&& f,
                                                  camp::idx_seq<Sequence...>)
{
  return f(::camp::get<Sequence>(std::forward<TupleLike>(tup))...);
}

CAMP_SUPPRESS_HD_WARN
template <typename Fn, typename TupleLike>
CAMP_HOST_DEVICE constexpr auto invoke(TupleLike&& tup, Fn&& f)
{
  return invoke_with_order(
      std::forward<TupleLike>(tup),
      std::forward<Fn>(f),
      camp::make_idx_seq_t<tuple_size<camp::decay<TupleLike>>::value>{});
}

namespace detail
{
  template <class T, class Tuple, idx_t... I>
  constexpr T make_from_tuple_impl(Tuple&& tup, idx_seq<I...>)
  {
    return T(::camp::get<I>(std::forward<Tuple>(tup))...);
  }
}  // namespace detail

template <class T, class Tuple>
constexpr T make_from_tuple(Tuple&& tup)
{
  return detail::make_from_tuple_impl<T>(
      std::forward<Tuple>(tup),
      make_idx_seq_t<tuple_size<type::ref::rem<Tuple>>::value>{});
}

template <class Fn, class TupleLike>
CAMP_HOST_DEVICE constexpr auto apply(Fn&& f, TupleLike&& tup)
{
  return ::camp::invoke(std::forward<TupleLike>(tup), std::forward<Fn>(f));
}

namespace internal
{
template <class Tuple, camp::idx_t... Idxs>
void print_tuple(std::ostream& os, Tuple const& tup, camp::idx_seq<Idxs...>)
{
  camp::sink((void*)&(os << (Idxs == 0 ? "" : ", ") << camp::get<Idxs>(tup))...);
}
}  // namespace internal
}  // namespace camp

template <class... Args>
auto operator<<(std::ostream& os, camp::tuple<Args...> const& tup)
    -> std::ostream&
{
  os << "(";
  ::camp::internal::print_tuple(os, tup, camp::make_idx_seq_t<sizeof...(Args)>{});
  return os << ")";
}

#if defined(__cplusplus) && __cplusplus >= 201703L
namespace std {
  template <typename... T>
  struct tuple_size<camp::tuple<T...> > {
    static constexpr size_t value = sizeof...(T);
  };

  template <size_t i, typename ... T>
  struct tuple_element<i, camp::tuple<T...>> {
    using type = decltype(camp::get<i>(camp::tuple<T...>{}));
  };

  template <typename TagList, typename... Elements>
  struct tuple_size<camp::tagged_tuple<TagList, Elements...> > {
    static constexpr size_t value = sizeof...(Elements);
  };

  template <size_t i, typename TagList, typename... Elements>
  struct tuple_element<i, camp::tagged_tuple<TagList, Elements...>> {
    using type = decltype(camp::get<i>(camp::tagged_tuple<TagList, Elements...>{}));
  };
} // namespace std
#endif

#endif /* camp_tuple_HPP__ */