结构化绑定宽度

问题描述 投票:0回答:3

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

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 将允许我们将它们使用到模板的参数中。你觉得怎么样?

未来Concepts有可能吗?

c++ variadic-templates type-traits c++17 structured-bindings
3个回答
10
投票
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_arity = details::maximize< 0, Cap, details::construct_searcher<T>::template result >;

这会在 0 到 20 之间对

T
的最长构造数量进行二分搜索。20 是一个常量,您可以根据编译时和内存成本随意增加它。

实例.

如果结构中的数据无法从其自身类型的右值构造,则它在 C++14 中不起作用,但我相信这里的 C++17 中会发生保证省略 (!)

将其转变为结构化绑定需要的不仅仅是一堆手动代码。 但是一旦你有了,你应该能够提出诸如“这

struct
的第三种类型是什么”之类的问题。

如果

struct
可以分解为结构化绑定,而无需完成
tuple_size
的工作,则它的数量决定了它需要多少个变量。

不幸的是,即使在 C++17 中,

std::tuple_size
也不是 SFINAE 友好的。 但是,使用
tuple_size
部分的类型也需要启用 ADL
std::get

创建一个带有

failure_tag get<std::size_t>(Ts const&...)
的命名空间
using std::get
。 使用它来检测它们是否覆盖了类型 (
get<0>
) 上的
!std::is_same< get_type<T,0>, failure_tag >{}
,如果是,则沿着
tuple_element
路径确定数量。 将结果元素填充到
std::tuple
decltype(get<Is>(x))
中并返回。

如果失败,请使用上面的

construct_arity
,并使用它来弄清楚如何在类型上使用结构化绑定。 然后我可能会把它发送到
std::tie
,以保持统一。

我们现在有了

tuple_it
,它可以接受任何类似结构化绑定的内容,并将其转换为引用或值的元组。 现在两条路径已经收敛,您的通用代码更容易!


3
投票

还有一种线性方法来查找“聚合数量”(尽管如此,也是在同样严格的情况下,如接受的答案中所示):

#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;
}

由于

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

实例.


2
投票

采用 Yakk 的想法并应用概念和模板化 lambda 等功能,使其更加紧凑:

template<std::size_t N>
struct anything {
    template<class T> operator T() const;
};

template<class T, std::size_t... Ints>
concept Constructible = requires {
    T{ anything<Ints>{}... };
};

template<class T, std::size_t N>
constexpr auto is_constructible() {
    constexpr auto unpack = [&]<std::size_t... Ints>(std::index_sequence<Ints...>) {
        return Constructible<T, Ints...>;
    };
    return unpack(std::make_index_sequence<N>{});
}

template<class T, std::size_t N = 0u, bool found = false>
constexpr auto find_struct_arity() {
    constexpr auto constructible = is_constructible<T, N>();

    if constexpr (found && !constructible) {
        return N - 1;
    }
    else if constexpr (constructible) {
        return find_struct_arity<T, N + 1, true>();
    }
    else {
        return find_struct_arity<T, N + 1, found>();
    }
}

这是对可构造性的线性搜索,如果它不再适用于

N
大括号,则会返回
N - 1
作为结果。使用示例(Godbolt 链接):

struct foo2 {
    int a;
    double b;
};
struct foo5 {
    int a;
    double b;
    foo2 c;
    char* d;
    std::array<int, 10> e;
};

int main() {
    static_assert(find_struct_arity<foo2>() == 2);
    static_assert(find_struct_arity<foo5>() == 5);
}
© www.soinside.com 2019 - 2024. All rights reserved.