草庐IT

c++ - 结构化绑定(bind)和引用元组

coder 2024-02-22 原文

我在设计一个简单的 zip 函数时遇到了一个问题,可以这样调用:

for (auto [x, y] : zip(std::vector{1,2,3}, std:vector{-1, -2, -3}) {
    // ...
}

所以 zip将返回类型为 zip_range 的对象,本身暴露beginend返回 zip_iterator 的函数.

现在,一个 zip_iterator ,正如我实现的那样,使用 std::tuple<Iterators> - 其中 Iterators 是压缩容器迭代器的类型 - 以跟踪其在压缩容器中的位置。当我取消引用 zip_iterator 时,我获得了对压缩容器元素的引用元组。问题是它不适合结构化绑定(bind)语法:

std::vector a{1,2,3}, b{-1, -2, -3};
for (auto [x, y] : zip(a, b)) { // syntax suggests by value
    std::cout << ++x << ", " << --y << '\n'; // but this affects a's and b's content
}

for (auto& [x, y] : zip(a, b)) { // syntax suggests by reference
    // fails to compile: binding lvalue ref to temporary
}

所以我的问题是:你能找到一种方法来协调这个引用元组的实际类型(临时值)与其语义(左值,允许修改它所引用的内容)吗?

我希望我的问题不会太宽泛。这是一个工作示例,用 clang++ prog.cc -Wall -Wextra -std=gnu++2a 编译(由于 gcc 处理演绎指南的方式存在错误,它不适用于 gcc):

#include <tuple>
#include <iterator>
#include <iostream>
#include <vector>
#include <list>
#include <functional>


template <typename Fn, typename Argument, std::size_t... Ns>
auto tuple_map_impl(Fn&& fn, Argument&& argument, std::index_sequence<Ns...>) {
    if constexpr (sizeof...(Ns) == 0) return std::tuple<>(); // empty tuple
    else if constexpr (std::is_same_v<decltype(fn(std::get<0>(argument))), void>) {
        [[maybe_unused]]
        auto _ = {(fn(std::get<Ns>(argument)), 0)...}; // no return value expected
        return;
    }
    // then dispatch lvalue, rvalue ref, temporary
    else if constexpr (std::is_lvalue_reference_v<decltype(fn(std::get<0>(argument)))>) {
        return std::tie(fn(std::get<Ns>(argument))...);
    }
    else if constexpr (std::is_rvalue_reference_v<decltype(fn(std::get<0>(argument)))>) {
        return std::forward_as_tuple(fn(std::get<Ns>(argument))...);
    }
    else {
        return std::tuple(fn(std::get<Ns>(argument))...);
    }
}

template <typename T>
constexpr bool is_tuple_impl_v = false;

template <typename... Ts>
constexpr bool is_tuple_impl_v<std::tuple<Ts...>> = true;

template <typename T>
constexpr bool is_tuple_v = is_tuple_impl_v<std::decay_t<T>>;


template <typename Fn, typename Tuple>
auto tuple_map(Fn&& fn, Tuple&& tuple) {
    static_assert(is_tuple_v<Tuple>, "tuple_map implemented only for tuples");
    return tuple_map_impl(std::forward<Fn>(fn), std::forward<Tuple>(tuple),
                          std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>());
}

template <typename... Iterators>
class zip_iterator {
    public:
    using value_type = std::tuple<typename std::decay_t<Iterators>::value_type...>;
    using difference_type = std::size_t;
    using pointer = value_type*;
    using reference = value_type&;
    using iterator_category = std::forward_iterator_tag;

    public:
    zip_iterator(Iterators... iterators) : iters(iterators...) {}
    zip_iterator(const std::tuple<Iterators...>& iter_tuple) : iters(iter_tuple) {}
    zip_iterator(const zip_iterator&) = default;
    zip_iterator(zip_iterator&&) = default;

    zip_iterator& operator=(const zip_iterator&) = default;
    zip_iterator& operator=(zip_iterator&&) = default;

    bool operator != (const zip_iterator& other) const { return iters != other.iters; }

    zip_iterator& operator++() { 
        tuple_map([](auto& iter) { ++iter; }, iters);
        return *this;
    }
    zip_iterator operator++(int) {
        auto tmp = *this;
        ++(*this);
        return tmp;
    }
    auto operator*() {
        return tuple_map([](auto i) -> decltype(auto) { return *i; }, iters);  
    }    
    auto operator*() const {
        return tuple_map([](auto i) -> decltype(auto) { return *i; }, iters);
    }
    private:
    std::tuple<Iterators...> iters;
};

template <typename... Containers>
struct zip {
    using iterator = zip_iterator<decltype(std::remove_reference_t<Containers>().begin())...>;
    template <typename... Container_types>
    zip(Container_types&&... containers) : containers_(containers...) {}
    auto begin() { return iterator(tuple_map([](auto&& i) { return std::begin(i); }, containers_)); }
    auto end()   { return iterator(tuple_map([](auto&& i) { return std::end(i); },   containers_)); }
    std::tuple<Containers...> containers_;
};

template <typename... Container_types>
zip(Container_types&&... containers) -> zip<std::conditional_t<std::is_lvalue_reference_v<Container_types>,
                                                             Container_types,
                                                             std::remove_reference_t<Container_types>>...>;

int main() {

    std::vector a{1,2,3}, b{-1, -2, -3};

    for (auto [x, y] : zip(a, b)) { // syntax suggests by value
        std::cout << x++ << ", " << y-- << '\n'; // but this affects a's and b's content
    }
    for (auto [x, y] : zip(a, b)) { 
        std::cout << x << ", " << y << '\n'; // new content
    }
    //for (auto& [x, y] : zip(a, b)) { // syntax suggests by reference
        // fails to compile: binding lvalue ref to temporary
    //}

}

最佳答案

从技术上讲,这不是结构化绑定(bind)问题,而是引用语义类型问题。 auto x = y看起来它确实是在复制然后作用于一个独立的类型,对于 tuple<T&...> 这样的类型来说显然不是这种情况。 (以及 reference_wrapper<T>string_viewspan<T> 等)。

然而,作为 T.C.在评论中建议,您可以做一些可怕的事情来完成这项工作。请注意,实际上不要这样做。我认为你的实现是正确的。但只是为了完整性。以及普遍的兴趣。

首先,结构化绑定(bind)的措辞表明了 get() 的不同之处。根据 underlying object 的值类别调用.如果它是左值引用(即 auto&auto const& ),则 get()在左值上调用。否则,它在 xvalue 上调用。我们需要通过以下方式利用这一点:

for (auto [x, y] : zip(a, b)) { ... }

做一件事,然后

for (auto& [x, y] : zip(a, b)) { ... }

做点别的。首先需要实际编译其他东西。为此,您的 zip_iterator::operator*需要返回一个左值。为此,它实际上需要在其中存储一个 tuple<T&...>。 .最简单的方法(在我看来)是存储一个 optional<tuple<T&...>>并且有operator*做一个 emplace()在上面并返回它的 value() .即:

template <typename... Iterators>
class zip_iterator {
    // ...
    auto& operator*() {
        value.emplace(tuple_map([](auto i) -> decltype(auto) { return *i; }, iters));
        return *value;
    }

    // no more operator*() const. You didn't need it anyway?

private:
    std::tuple<Iterators...> iters;

    using deref_types = std::tuple<decltype(*std::declval<Iterators>())...>;
    std::optional<deref_types> value;
};

但这仍然给我们带来了想要不同 get() 的问题秒。为了解决这个问题,我们需要自己的 tuple类型 - 提供自己的 get() s,这样当调用一个左值时它产生左值,但是当调用一个 xvalue 时它产生纯右值。

我认为是这样的:

template <typename... Ts>
struct zip_tuple : std::tuple<Ts...> {
    using base = std::tuple<Ts...>;
    using base::base;

    template <typename... Us,
         std::enable_if_t<(std::is_constructible_v<Ts, Us&&> && ...), int> = 0>
    zip_tuple(std::tuple<Us...>&& rhs)
         : base(std::move(rhs))
    { }

    template <size_t I>
    auto& get() & {
        return std::get<I>(*this);
    };

    template <size_t I>
    auto& get() const& {
        return std::get<I>(*this);
    };

    template <size_t I>
    auto get() && {
        return std::get<I>(*this);
    };

    template <size_t I>
    auto get() const&& {
        return std::get<I>(*this);
    };
};

namespace std {
    template <typename... Ts>
    struct tuple_size<zip_tuple<Ts...>>
        : tuple_size<tuple<Ts...>>
    { };

    template <size_t I, typename... Ts>
    struct tuple_element<I, zip_tuple<Ts...>>
        : tuple_element<I, tuple<remove_reference_t<Ts>...>>
    { };
}

在非左值引用的情况下,这意味着我们绑定(bind)了一堆 rvalue references到临时工,这很好——他们的生命周期得到延长。

现在只改变 deref_types别名为 zip_tuple而不是 std::tuple并且你有你想要的行为。


两个不相关的笔记。

1) 您的演绎指南可以简化为:

template <typename... Container_types>
zip(Container_types&&... containers) -> zip<Container_types...>;

如果Container_types不是左值引用类型,那么它就不是引用类型,remove_reference_t<Container_types> Container_types .

2) gcc 有 a bug关于你试图构建 zip<> 的方式.因此,要使其与两者一起编译,请选择:

template <typename... Containers>
struct zip {
    zip(Containers... containers) : containers_(std::forward<Containers>(containers)...) { }
};

无论如何,您的预期用途是通过演绎指南,所以这不应该让您付出任何代价来支持它在多个编译器上工作。

关于c++ - 结构化绑定(bind)和引用元组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49628401/

有关c++ - 结构化绑定(bind)和引用元组的更多相关文章

  1. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  2. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  3. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

    它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

  4. ruby - 一个 YAML 对象可以引用另一个吗? - 2

    我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的ruby​​yaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir

  5. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  6. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  7. ruby-on-rails - 创建 ruby​​ 数据库时惰性符号绑定(bind)失败 - 2

    我正在尝试在Rails上安装ruby​​,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf

  8. ruby-on-rails - 一般建议和推荐的文件夹结构 - Sinatra - 2

    您将如何构建一个简单的Sinatra应用程序?我正在制作,我希望该应用具有以下功能:“应用程序”更像是一个包含所有信息的管理仪表板。然后另一个应用程序将通过REST访问信息。我还没有创建仪表板,只是从数据库中获取东西session和身份验证(尚未实现)您可以上传图片,其他应用可以显示这些图片我已经使用RSpec创建了一个测试文件通过Prawn生成报告目前的设置是这样的:app.rbtest_app.rb因为我实际上只有应用程序和测试文件。到目前为止,我已经将Datamapper用于ORM,将SQLite用于数据库。这是我的第一个Ruby/Sinatra项目,所以欢迎任何和所有建议-我应

  9. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

  10. ruby - Chef LW 资源属性默认值如何引用另一个属性? - 2

    我正在尝试将一个资源属性的默认值设置为另一个属性的值。我正在为我正在构建的tomcat说明书定义一个资源,其中包含以下定义。我想要可以独立设置的“名称”和“服务名称”属性。当未设置服务名称时,我希望它默认为为“名称”提供的任何内容。以下不符合我的预期:attribute:name,:kind_of=>String,:required=>true,:name_attribute=>trueattribute:service_name,:kind_of=>String,:default=>:name注意第二行末尾的“:default=>:name”。当我在Recipe的新block中引用我

随机推荐