c++ - 如何定义一个可以结构化绑定(bind)的对象的概念?

标签 c++ c++20 c++-concepts

我想定义一个 concept即可以检测类型T可以是结构化绑定(bind)与否:

template <typename T>
concept two_elements_structured_bindable = requires (T t) {
  auto [x, y] = t;
};
但这无法编译。是否有正确的方法来定义 concept像那样?

最佳答案

使用 C++20 , 你可以定义一个 concept这会将 C 风格的数组和类似元组的类型识别为结构可绑定(bind)的。但它无法识别基于公共(public)字段可绑定(bind)结构的类型。
可以实现的可能概念(参见完整实现 here):

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>();

附注:它可以通过编译器内在特性来实现
for example - here for clang (由 Avi Lachmish 提供)。

亲爱的 friend @Dvir Yitzchaki向我指出 基于 Herb Sutter 提出的模式匹配语法,您可以根据 as 识别所有结构可绑定(bind)案例检查内部 concept , 还没有 C++20 ,但已经实现 in Circle compiler .
Herb Sutter 与 Circle 编译器实现者 Sean Baxter 一起提出了 is 的想法。和 as模式匹配,作为 Herb 在 CppCon 2021 上演讲的一部分,see here .
根据他们的谈话,Dvir 想到了我后来详细阐述的想法 working implementation on Circle compiler :
template <typename T>
concept two_elements_structured_bindable = structured_bindable<T>
    && !single_element_structured_bindable<T>
    && two_elements_structured_bindable_<T>;
基于此:
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]};
};
和:
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
          ]};
};
请注意,它支持检查所有类型的结构绑定(bind):
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>);

a CoreCpp meetup 中提出上述解决方案后, Dvir 将冷水倒在我的溶液上,使用 a much shorter one :
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];
};
我仍然保留上面的长解决方案,因为在我看来它对其他实现有一些值(value)。

如果你想限制你要绑定(bind)的类型,你可能更喜欢使用另一种方法,它再次依赖于模式匹配语法,另一种建议 concept :
template <typename T, typename... Ts>
concept TupleLike = requires (T t) {
    {t as [Ts...]}; // note: not supported by C++20
};
这可以允许这样的约束:
void foo(TupleLike<double, double, double> auto tup) {
    auto[a, b, c] = tup; // 3 doubles
    // ...
}
Above code再次基于模式匹配语法,在 C++ 中尚不可用(从 C++20 开始),但已经在 Circle 编译器中实现。

现在,为了支持 structured_bindable<Size> 的更通用的概念,需要使用另一个 future 的 C++ 特性,它允许 sizeof...(T)T那不是可变参数包,而是任何结构可绑定(bind)类型。此功能可能是 p1858 的一部分或相关提案。再说一次,它是 already supported in Circle compiler .
这允许 this very simple implementation (再次,由 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>;
因此允许限制可绑定(bind)的精确给定数字,例如:
void foo(const structured_bindable_with<2> auto& v) {
    const auto&[a, b] = v;
    std::cout << a << ", " << b << std::endl;
}
其实如果能提供sizeof...成为一个特征,表明你是一个结构可绑定(bind)类型(包括可变参数包本身!),然后上面的概念 can simply become :
template <typename T, size_t SIZE>
concept structured_bindable_with = (sizeof...(T) == SIZE);

关于c++ - 如何定义一个可以结构化绑定(bind)的对象的概念?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63369361/

相关文章:

c++ - 对 'cv::viz::Viz3d::Viz3d(std::string&const)' 的 undefined reference

C++ 从 CSV 文件中读取一列数据

c++ - Int 到 Base 2 & 8 优化

c++ - 通过指针与数组引用进行数组索引序列扩展

c++ - 更改模板参数中 bool 值的类型

c++ - 当模板类 is_convertible 为众所周知的类型时,特化仿函数

C++概念不好用?

c++ - 如何从 QAbstractItemView 中删除小部件

c++ - SFINAE 内部概念模板参数

c++ - 为什么 Ranges 库中的 std::views::take_while 需要 const 谓词?