【问题标题】:Structured bindings width结构化绑定宽度
【发布时间】:2017-02-07 16:10:45
【问题描述】:

是否可以使用结构化绑定语法来确定我应该在方括号中指定多少个变量名,以匹配普通右侧 struct 的数据成员数?

我想成为通用库的一部分,它使用结构化绑定将任意类分解为其组成部分。目前还没有结构化绑定的可变参数版本(而且,我认为,不能用于当前建议的语法),但我的第一个想法是对某些函数 decompose() 进行一组重载,它执行 struct 的分解参数转化为一组它的成分。 decompose() 应该被参数的数量(即struct)数据成员重载。目前constexpr if 语法也可以用来调度这个。但是为了上述目的,我怎样才能模拟类似于sizeof... 运算符的东西呢?我不能在 SFINAE 结构中的某处使用auto [a, b, c] 语法,因为它是分解声明,AFAIK 任何声明都不能在decltype 中使用,我也不能在正文中将其用于我的目的lambda 函数,因为 lambda 函数也不能在模板参数中使用。

我当然想要内置运算符(语法类似于sizeof[] S/sizeof[](S) 用于class S),但也可以接受以下内容:

template< typename type, typename = void >
struct sizeof_struct
{

};

template< typename type >
struct sizeof_struct< type, std::void_t< decltype([] { auto && [p1] = std::declval< type >(); void(p1); }) > >
    : std::integral_constant< std::size_t, 1 >
{

};

template< typename type >
struct sizeof_struct< type, std::void_t< decltype([] { auto && [p1, p2] = std::declval< type >(); void(p1); void(p2);  }) > >
    : std::integral_constant< std::size_t, 2 >
{

};

... etc up to some reasonable arity

也许constexpr lambda 将允许我们将它们用于模板的参数。你怎么看?

即将到来的概念有可能吗?

【问题讨论】:

  • AFAIK lambda 不会在 decltype 中可用,不是因为它们不是 constexpr,而是因为每次使用 lambda 语法都会创建一个全新的类型,这将混淆模板实例化逻辑一个很多
  • @Quentin 很伤心。我认为这会很好,如果任何文字类型的值都可以成为非类型模板参数。我确信会有一些问题,但是这种模板的实例可能会被禁止用于任何类型的导出。在这种情况下,它们仍然可以用于通用代码。
  • 我在这里之前的问答中找到了partial answer(对于没有(甚至是空的)基数的类是正确的)。但是问题的其余部分的答案是什么?
  • Here 是一个可能的用例。
  • @W.F.不,这意味着如果你想让你的结构被分解,并且默认不会削减它(例如,它没有所有公共数据成员,那些数据成员不来自同一个类,并非所有人都应该参与,或者您希望其中一些人使用不同的类型),您专攻tuple_size/tuple_element/get

标签: c++ variadic-templates typetraits c++17 structured-bindings


【解决方案1】:
struct two_elements {
  int x;
  double y;
};

struct five_elements {
  std::string one;
  std::unique_ptr<int> two;
  int * three;
  char four;
  std::array<two_elements, 10> five;
};

struct anything {
  template<class T> operator T()const;
};

namespace details {
  template<class T, class Is, class=void>
  struct can_construct_with_N:std::false_type {};

  template<class T, std::size_t...Is>
  struct can_construct_with_N<T, std::index_sequence<Is...>, std::void_t< decltype(T{(void(Is),anything{})...}) >>:
  std::true_type
  {};
}
template<class T, std::size_t N>
using can_construct_with_N=details::can_construct_with_N<T, std::make_index_sequence<N>>;

namespace details {
  template<std::size_t Min, std::size_t Range, template<std::size_t N>class target>
  struct maximize:
    std::conditional_t<
      maximize<Min, Range/2, target>{} == (Min+Range/2)-1,
      maximize<Min+Range/2, (Range+1)/2, target>,
      maximize<Min, Range/2, target>
    >
  {};
  template<std::size_t Min, template<std::size_t N>class target>
  struct maximize<Min, 1, target>:
    std::conditional_t<
      target<Min>{},
      std::integral_constant<std::size_t,Min>,
      std::integral_constant<std::size_t,Min-1>
    >
  {};
  template<std::size_t Min, template<std::size_t N>class target>
  struct maximize<Min, 0, target>:
    std::integral_constant<std::size_t,Min-1>
  {};

  template<class T>
  struct construct_searcher {
    template<std::size_t N>
    using result = ::can_construct_with_N<T, N>;
  };
}

template<class T, std::size_t Cap=20>
using construct_airity = details::maximize< 0, Cap, details::construct_searcher<T>::template result >;

这将对T 的最长构造空气从0 到20 进行二分搜索。20 是一个常数,您可以根据编译时间和内存成本随意增加它。

Live example.

如果您的结构中的数据不能从其自身类型的右值构造,它在 C++14 中将无法工作,但我相信在 C++17 中会出现 guanteed elision (!)

将其转换为结构化绑定需要的不仅仅是一堆手动代码。但是一旦你有了,你应该可以问诸如“这个struct的第三种类型是什么”之类的问题。

如果 struct 可以在不完成 tuple_size 的工作的情况下分解为结构化绑定,那么它的通畅性决定了它需要多少变量。

不幸的是,std::tuple_size 即使在 C++17 中也对 SFINAE 不友好。但是,使用 tuple_size 部分的类型也需要启用 ADL std::get

使用failure_tag get&lt;std::size_t&gt;(Ts const&amp;...)using std::get 创建一个命名空间。使用它来检测他们是否已经覆盖了类型 (!std::is_same&lt; get_type&lt;T,0&gt;, failure_tag &gt;{}) 上的 get&lt;0&gt;,如果是,则沿着 tuple_element 路径确定通风。将生成的元素填充到 std::tupledecltype(get&lt;Is&gt;(x)) 中并返回。

如果失败,请使用上面的construct_airity,并使用它来确定如何在类型上使用结构化绑定。为了统一起见,我可能会将其发送到std::tie

我们现在有了tuple_it,它接受任何类似结构化绑定的东西并将其转换为引用或值的元组。 现在两条路径已经融合,您的通用代码更容易了!

【讨论】:

  • 结构化绑定的默认版本允许的不仅仅是聚合;所讨论的类只需要 1) 具有所有公共数据成员,并且 2) 让它们都是同一类的直接成员。
  • 您可以使用乘以 2 直到 arity 2^n 失败。然后将二进制搜索应用于 2^n 范围的上半部分。
  • 正如我上面提到的,它已经完成了here。此外,我认为更简单的线性方法更适合实际使用(就编译时间而言)。
  • 不幸的是,由于大括号省略,此方法在具有普通数组的结构中失败。使用struct with_arr { int a, b, c, d[5]; };construct_airity 报告 8,但您需要 4 个名称来解构它。 Proof
  • @joao 是的。为巴里的open-std.org/JTC1/SC22/WG21/docs/papers/2018/p1061r0.html 提案工作。
【解决方案2】:

还有一种线性方法可以找到“聚合数量”(不过,在同样严格的情况下,如已接受的答案):

#include <type_traits>
#include <utility>
#include <tuple>

struct filler { template< typename type > operator type && (); };

template< typename aggregate, 
          typename index_sequence = std::index_sequence<>, 
          typename = void >
struct aggregate_arity
        : index_sequence
{

};

template< typename aggregate, 
          std::size_t ...indices >
struct aggregate_arity< aggregate, 
                        std::index_sequence< indices... >, 
                        std::void_t< decltype(aggregate{(indices, std::declval< filler >())..., std::declval< filler >()}) > >
    : aggregate_arity< aggregate, 
                       std::index_sequence< indices..., sizeof...(indices) > >
{

};

template< std::size_t index, typename type >
constexpr
decltype(auto)
get(type & value) noexcept
{
    constexpr std::size_t arity = aggregate_arity< std::remove_cv_t< type > >::size();
    if constexpr (arity == 1) {        
        auto & [p1] = value;
        if constexpr (index == 0) {
            return (p1);
        } else {
            return;
        }
    } else if constexpr (arity == 2) {
        auto & [p1, p2] = value;
        if constexpr (index == 0) {
            return (p1);
        } else if constexpr (index == 1) {
            return (p2);
        } else {
            return;
        }
    } else if constexpr (arity == 3) {
        auto & [p1, p2, p3] = value;
        if constexpr (index == 0) {
            return (p1);
        } else if constexpr (index == 1) {
            return (p2);
        } else if constexpr (index == 2) {
            return (p3);
        } else {
            return;
        }
    } else /* extend it by yourself for higher arities */ {
        return;
    }
}

// main.cpp
#include <cstdlib>
#include <cassert>

namespace
{

using S = struct { int i; char c; bool b; };

S s{1, '2', true};

decltype(auto) i = get< 0 >(s);
decltype(auto) c = get< 1 >(s);
decltype(auto) b = get< 2 >(s);

static_assert(std::is_same< decltype(i), int & >{});
static_assert(std::is_same< decltype(c), char & >{});
static_assert(std::is_same< decltype(b), bool & >{});

static_assert(&i == &s.i);
static_assert(&c == &s.c);
static_assert(&b == &s.b);

}

int
main()
{
    assert(i == 1);
    assert(c == '2');
    assert(b == true);
    return EXIT_SUCCESS;
}

当前get() 的参数不能有const 顶级类型限定符(即type 可以是&amp;&amp;&amp;,但不能是const &amp;const &amp;&amp;),因为bug .

Live example.

【讨论】:

  • 嗨,我无法让它与派生类型一起使用:struct A{};结构 B : A{int i;} a;自动& i = get(a);这不编译:“标识符的数量必须与结构化绑定声明中的数组元素或成员的数量相匹配”你知道如何解决这个问题吗?
  • @Fabian 也许这行得通:B b; auto &amp; [a, i] = b; static_assert(std::is_same&lt; decltype(a), A &amp; &gt;::value, "!"); static_assert(std::is_same&lt; decltype(i), int &amp; &gt;::value, "!");?
  • @Fabian 似乎,这是不可能用aggregate_arity 方法实现的。结构化绑定的聚合初始化和分解之间的“占位符”数量存在很大差异。
  • 感谢您的回答,我放弃了继承方法并尝试了其他方法(也失败了 :() 请看一下:stackoverflow.com/questions/46675239
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-05-15
  • 2017-04-02
  • 1970-01-01
  • 2022-12-15
  • 2011-06-29
相关资源
最近更新 更多