如何定义可以结构化绑定的对象的概念?

康桓瑋*_*康桓瑋 5 c++ c++-concepts c++20

我想定义一个concept可以检测类型是否T可以是结构化绑定的

template <typename T>
concept two_elements_structured_bindable = requires (T t) {
  auto [x, y] = t;
};
Run Code Online (Sandbox Code Playgroud)

但这无法编译。有没有合适的方法来定义concept这样的?

Ami*_*rsh 8

使用 C++20,您可以定义将C 样式数组类元组类型标识concept绑定结构的。但它无法识别基于仅限公共字段的结构可绑定类型。

可以实现的可能概念(请参阅此处的完整实现):

template<typename T, std::size_t N>
concept structure_bindable = 
    (std::is_array_v<T> && (std::extent_v<T> == N)) ||
    ((std::tuple_size_v<T> == N) && has_get_for_size<T, N>::value);

template<typename T, typename... Ts>
concept structure_bindable_with = 
    structure_bindable<T, sizeof...(Ts)>
    && is_get_N<T, Ts...>(std::make_index_sequence<sizeof...(Ts)>{});

template<typename T, size_t N, typename Expected>
concept structure_bindable_with_N = 
    structure_bindable<T, N>
    && is_get_N<T, N-1, Expected>();
Run Code Online (Sandbox Code Playgroud)

旁注:它可以通过编译器内在功能来实现, 例如 - 这里是 clang(由 Avi Lachmish 提供)。


我亲爱的朋友@Dvir Yitzchaki向我指出,基于 Herb Sutter 提出的模式匹配语法,您可以基于a 内部的检查来识别所有结构可绑定情况,尚未在 C++20 中使用,但已在 Circle 编译器中实现。asconcept

isHerb Sutter 与 Circle 编译器实现者 Sean Baxter 一起提出了和模式匹配的想法as,作为 Herb 在 CppCon 2021 上演讲的一部分,请参阅此处

根据他们的谈话,Dvir 得出了一个想法,我后来在 Circle 编译器上详细阐述了这个工作实现

template <typename T>
concept two_elements_structured_bindable = structured_bindable<T>
    && !single_element_structured_bindable<T>
    && two_elements_structured_bindable_<T>;
Run Code Online (Sandbox Code Playgroud)

基于此:

template <typename T>
concept structured_bindable = requires (T t) {
    t as [...]; // note: not supported by C++20
};

template <typename T>
struct single_element_structured_bindable_wrapper {
    auto first() {
        auto[a, ...] = std::declval<T>();  // note: not supported by C++20
        return a;
    }
    using first_type = decltype(first());
};

template <typename T>
concept single_element_structured_bindable = structured_bindable<T>
    && requires (T t) {
    {t as [single_element_structured_bindable_wrapper<T>::first_type]};
};
Run Code Online (Sandbox Code Playgroud)

和:

template <typename T>
struct two_elements_structured_bindable_wrapper {
    auto first() {
        auto[a, ...] = std::declval<T>(); // note: not supported by C++20
        return a;
    }
    auto second() {
        auto[a, b, ...] = std::declval<T>(); // note: not supported by C++20
        return b;
    }
    using first_type = decltype(first());
    using second_type = decltype(second());
};

template <typename T>
concept two_elements_structured_bindable_ = requires (T t) {
    {t as [
            two_elements_structured_bindable_wrapper<T>::first_type,
            two_elements_structured_bindable_wrapper<T>::second_type
          ]};
};
Run Code Online (Sandbox Code Playgroud)

请注意,它支持检查所有类型的结构绑定:

static_assert(!two_elements_structured_bindable<std::tuple<int, int, int>>);
static_assert(!two_elements_structured_bindable<std::tuple<int>>);
static_assert(!two_elements_structured_bindable<int>);
static_assert(two_elements_structured_bindable<std::tuple<int, int>>);

static_assert(!two_elements_structured_bindable<std::array<int, 3>>);
static_assert(!two_elements_structured_bindable<std::array<int, 1>>);
static_assert(two_elements_structured_bindable<std::array<int, 2>>);

struct Vec3 { float x, y, z; };    
static_assert(!two_elements_structured_bindable<Vec3>);

struct Vec2 { float x, y; };
static_assert(two_elements_structured_bindable<Vec2>);
Run Code Online (Sandbox Code Playgroud)

在CoreCpp 聚会上提出上述解决方案后,Dvir 给我的解决方案泼了一盆冷水,其中的解决方案要短得多

struct anything // std::any is not good enough for that
{
    template<typename T>
    anything(T&&) {}
};

template<typename T>
concept twople = requires(T t)
{
    t as [anything, anything];
};
Run Code Online (Sandbox Code Playgroud)

我仍然保留上面的长解决方案,因为我认为它对其他实现有一定的价值。


如果您想限制要绑定的类型,您可能更喜欢使用另一种方法,该方法再次依赖于模式匹配语法,并提出了另一种方法concept

template <typename T, typename... Ts>
concept TupleLike = requires (T t) {
    {t as [Ts...]}; // note: not supported by C++20
};
Run Code Online (Sandbox Code Playgroud)

这可以允许这样的约束:

void foo(TupleLike<double, double, double> auto tup) {
    auto[a, b, c] = tup; // 3 doubles
    // ...
}
Run Code Online (Sandbox Code Playgroud)

上面的代码同样基于模式匹配语法,在 C++ 中尚不可用(从 C++20 开始),但已在 Circle 编译器中实现。


现在,为了支持 的更通用概念structured_bindable<Size>,需要使用另一个未来的 C++ 功能,该功能允许sizeof...(T)不是T可变参数包,而是任何结构可绑定类型。此功能可能是p1858或相关提案的一部分。再说一遍,Circle 编译器已经支持它了。

这允许这个非常简单的实现(再次由 Dvir 提出):

template <typename T, size_t SIZE>
concept has_size_of = sizeof...(T) == SIZE;

template <typename T>
concept structured_bindable = requires (T t) {
    t as [...];
};

template <typename T, size_t SIZE>
concept structured_bindable_with = structured_bindable<T> && has_size_of<T, SIZE>;
Run Code Online (Sandbox Code Playgroud)

因此允许限制可与确切的给定数字绑定,例如:

void foo(const structured_bindable_with<2> auto& v) {
    const auto&[a, b] = v;
    std::cout << a << ", " << b << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

事实上,如果能够提供sizeof...成为一个功能,表明您是一个结构可绑定类型(包括变量包本身!),那么上述概念可以简单地变成

template <typename T, size_t SIZE>
concept structured_bindable_with = (sizeof...(T) == SIZE);
Run Code Online (Sandbox Code Playgroud)


Bar*_*rry 4

不。

结构化绑定分为三种情况:

  1. 数组。这很容易检测到。

  2. 类似元组。您可以轻松检查是否std::tuple_size<E>::value有效,然后检查std::tuple_element<I, E>::type作为所有正确类型的类型是否有效。但这种get情况比较困难,因为你必须处理会员与非会员的问题……但除此之外,我认为可行。

  3. 具有所有公共(是的,技术上可访问)成员作为同一类的直接成员的类型。以现有技术无法检测到这一点。magic_get我认为可以处理,struct X { int a, b; };但您struct Y : X { };也不struct Z { X& x; };需要进行适当的反思来检查这种情况。

从 C++20 开始,您将需要某种编译器内部函数来执行此操作。