4

我想为类模板的内部类提供结构化绑定。我怎样才能专攻std::tuple_size那个内部课程?

我不能对数据成员使用结构化绑定,因为内部类可能是与该功能不兼容的类型。所以我需要提供类似元组的结构化绑定。为了向内部类提供这样的功能,我需要部分专注std::tuple_sizenamespace std. 问题是我得到了(外部类的)参数的非推断上下文。T

我知道我可以将内部类放在全局命名空间中,从而解决所有问题,但是有没有办法在保持类内部时获得相同的结果?

#include <tuple>

template<typename T>
class Collection
{
    public:
        struct Element
        {
            int id;
            T actual_element;
        };

    //...
};


namespace std 
{
    template<typename T>
    struct tuple_size<typename Collection<T>::Element> // Error! Non-deduced context
        : std::integral_constant<std::size_t, 2> {};
}



//Collection iterators...

int main()
{
    Collection<int> collection;

    for (auto & [id, element] : collection) //what I'd like to achieve
        //do some stuff...
}
4

2 回答 2

3

您不必为这种情况提供绑定:Element已经可以按原样分解:

struct Element { int i, j; };
auto [i, j] = Element{2, 3}; // ok

但是,假设Element实际上更复杂并且需要自定义绑定,那么是的 - 您需要将其移出。但是,它不需要位于全局命名空间中。它可能在其他地方:

namespace detail {
    template <typename T> struct Element { ... };
}

template<typename T>
class Collection
{
    public:
        using Element = detail::Element<T>;
        friend Element;
        // ...
};

到那时,专门化绑定就很简单了。没有办法因为,正如您所指出的,专攻Collection<T>::Element是一个非推导的上下文。


缺少一种新的语言功能,可让您选择加入类主体内的结构化绑定。有这样的论文P1096,但提交时被拒绝。这并不是说新提案不能做得更好。

于 2019-01-04T19:58:46.043 回答
0

我不认为下面的解决方案是该问题的最优雅(或根本优雅)的解决方案。然而,它通过使用/利用 C++20 概念来实现为Element模板类的内部类添加结构绑定支持来实现这个非常特定的目的。Collection

鉴于get成员函数也可以声明为自由函数,因此可以使用以下模式为无法/可能无法修改的内部类(例如,遗留、第 3 方)添加结构绑定支持。这个概念应该相应地调整。

在线试用

代码

#include <concepts>
#include <tuple>

struct Hack
{};

template< typename T >
concept CollectionElement = requires
{
   typename T::type;
   { T::hack } -> std::same_as< const Hack& >;
};

namespace std
{
  template< ::CollectionElement T >
  class tuple_size< T > : public integral_constant< size_t, 2 >
  {};

  template< ::CollectionElement T >
  struct tuple_element< 0u, T >
  {
      using type = int;
  };

  template< ::CollectionElement T >
  struct tuple_element< 1u, T >
  {
      using type = typename T::type;
  };
}

template<typename T>
class Collection
{
    public:

        struct Element
        {
            static constexpr Hack hack = {};
            using type = T;

            template< std::size_t I >
            std::tuple_element_t< I, Element >& get() &
            {
                if constexpr (I == 0u) return id;
                if constexpr (I == 1u) return actual_element;
            }

            template< std::size_t I >
            const std::tuple_element_t< I, Element >& get() const&
            {
                if constexpr (I == 0u) return id;
                if constexpr (I == 1u) return actual_element;
            }

            template< std::size_t I >
            std::tuple_element_t< I, Element >& get() &&
            {
                if constexpr (I == 0u) return id;
                if constexpr (I == 1u) return actual_element;
            }

            template< std::size_t I >
            const std::tuple_element_t< I, Element >& get() const&&
            {
                if constexpr (I == 0u) return id;
                if constexpr (I == 1u) return actual_element;
            }
            
            int id;
            T actual_element;
        };

    //...
};

int main()
{
    Collection< int >::Element test
    {
        .id = 3,
        .actual_element = 5
    };

    auto& [id, element] = test;
    id = 7;
    element = 9;
    
    return id + element; // returns 16
}

一般原则

#include <concepts>
#include <tuple>

template< typename T >
struct Outer
{
    using value_type = T;

    struct Inner
    {
        using value_type = typename Outer::value_type;

        int m_first  = {};
        int m_second = {};
    };
};

namespace std
{
  template< typename T >
  requires(std::same_as< T, typename ::Outer< typename T::value_type >::Inner >)
  class tuple_size< T > : public integral_constant< size_t, 2 >
  {};

  template< typename T >
  requires(std::same_as< T, typename ::Outer< typename T::value_type >::Inner >)
  struct tuple_element< 0u, T >
  {
      using type = int;
  };

  template< typename T >
  requires(std::same_as< T, typename ::Outer< typename T::value_type >::Inner >)
  struct tuple_element< 1u, T >
  {
      using type = int;
  };
}

template< std::size_t I, typename T >
requires(std::same_as< T, typename Outer< typename T::value_type >::Inner >)
[[nodiscard]]
inline std::tuple_element_t< I, T >& get(T& src)
{
    if constexpr (I == 0u)
    {
        return src.m_first;
    }
    if constexpr (I == 1u)
    {
        return src.m_second;
    }
}

template< std::size_t I, typename T >
requires(std::same_as< T, typename Outer< typename T::value_type >::Inner >)
[[nodiscard]]
inline const std::tuple_element_t< I, T >& get(const T& src)
{
    if constexpr (I == 0u)
    {
        return src.m_first;
    }
    if constexpr (I == 1u)
    {
        return src.m_second;
    }
}

template< std::size_t I, typename T >
requires(std::same_as< T, typename Outer< typename T::value_type >::Inner >)
[[nodiscard]]
inline std::tuple_element_t< I, T >&& get(T&& src)
{
    if constexpr (I == 0u)
    {
        return src.m_first;
    }
    if constexpr (I == 1u)
    {
        return src.m_second;
    }
}

template< std::size_t I, typename T >
requires(std::same_as< T, typename Outer< typename T::value_type >::Inner >)
[[nodiscard]]
inline const std::tuple_element_t< I, T >&& get(const T&& src)
{
    if constexpr (I == 0u)
    {
        return src.m_first;
    }
    if constexpr (I == 1u)
    {
        return src.m_second;
    }
}

int main()
{
    Outer< int >::Inner ref;
    auto& [f, s] = ref;
    f = 3; s = 5;
    
    return ref.m_first + ref.m_second;
}
于 2021-03-27T15:05:35.313 回答