【问题标题】:How to detect the last iteration of std::map using structured bindings from C++17?如何使用 C++17 的结构化绑定检测 std::map 的最后一次迭代?
【发布时间】:2021-11-13 07:40:49
【问题描述】:

如何使用结构化绑定检测地图的最后一次迭代?

这是一个具体的例子:我有以下简单的代码,我使用 C++17 的结构化绑定从 std::map 打印元素:

#include <iostream>
#include <map>
#include <string>

int main() {
    
    std::map<std::string, size_t> mymap {{"a", 0}, {"b", 1}, {"c", 2}, {"d", 3}};
   
    // using structured bindings, C++17

    for (auto const& [key, value] : mymap) {
        std::cout << "key: " << key << ", value: " << value << ", ";
    }

    return 0;
}

问题是,这将导致尾随逗号,即

key: a, value: 0, key: b, value: 1, key: c, value: 2, key: d, value: 3, 

受这个问题的启发:How can I detect the last iteration in a loop over std::map?,我可以编写带有迭代器的代码来打印出 std::map 的内容,而无需尾随逗号,即

for (auto iter = mymap.begin(); iter != mymap.end(); ++iter){
    // detect final element
    auto last_iteration = (--mymap.end());
    if (iter==last_iteration) {
        std::cout << "\"" << iter->first << "\": " << iter->second;
    } else {
        std::cout << "\"" << iter->first << "\": " << iter->second << ", ";
    }
}

如何使用for (auto const&amp; [key, value] : mymap) 做到这一点?如果我知道std::map 中的最后一个键,我可以为它写一个条件;但是有没有其他方法不用iter

【问题讨论】:

  • @xskxzr 谢谢你的回答。我想我还是有点困惑;你能解释更多吗?
  • boost::algorithm::join 可能会提供一些灵感。
  • 注意:下面有很多很棒的答案。我不得不将一个标记为已接受,但我要感谢大家的帮助。

标签: c++ stl c++17


【解决方案1】:

Yakk 的回答启发我使用 Ranges 编写 iterators_of,但不引入新类:

#include <iostream>
#include <map>
#include <string>
#include <ranges>

template<class Range>
auto iterators_of(Range&& r){
    return std::ranges::views::iota(std::begin(r), std::end(r));
}

int main() {
    
    std::map<std::string, size_t> mymap {{"a", 0}, {"b", 1}, {"c", 2}, {"d", 3}};

    for(auto it : iterators_of(mymap) ) {
        auto const& [key, value] = *it;
        if(std::next(it) != mymap.end()) {std::cout << "key: " << key << ", value: " << value << ", ";}
        else                             {std::cout << "key: " << key << ", value: " << value;}
    }
    return 0;
}

https://godbolt.org/z/rx15eMnWh

【讨论】:

  • 很好,iota 的弱可增量要求几乎是迭代器和这里需要的整数共有的属性的子集。有道理,作为我的索引,我也用来制作整数范围,范围中的整数范围工具也可以工作。
  • @Yakk-AdamNevraumont 是的,没错,迭代器是比整数更弱的概念(就算术而言)。有趣的是,这种用法打开了一罐奇怪但可能一致的语义 IMO。原因是,在 Ranges 语法中,如果 iota(std::begin(r), std::end(r)); 有效,那么 iota(r); 也应该有效。现在iota(r) 已经意味着'increment "r"' 没有限制。
  • @Yakk-AdamNevraumont,这意味着iota(n) -&gt; n, n + 1, n + 2...。这意味着begin(n) 应该是n(例如整数),还有*n -&gt; n,以及扩展名n[m] -&gt; n + m。还有end(n) -&gt; numeric_limit::max()。令人难以置信。 (Stepanov 是对的,对于任何非指针事物,解引用应该返回 self 默认情况下)。
  • 我真的很喜欢这个答案,使用range::iota。我希望我也可以为@Yakk-AdamNevraumont(以及其他好的答案)打分以获得接受的答案。
  • @EB2127 别担心。我已经有大约无限的互联网点。
【解决方案2】:

对于除第一个之外的所有键和值对,在开头打印, 怎么样?

IF first_key_value_pair:
    print(key, value)
ELSE:
    print(',' + (key, value))

我们可以使用布尔值来处理第一个键值对。

 std::map<char, int> m = {{'a', 1}, {'b', 1}, {'c', 1}, {'d', '1'}};
 bool first = true; 

 for(const auto& [key, value] : m){
    if(first) first = false; else std::cout << ", ";
    std::cout << "Key: " << key << ", Value: " << value;
 }

输出:Key: a, Value: 1, Key: b, Value: 1, Key: c, Value: 1, Key: d, Value: 49

【讨论】:

  • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
【解决方案3】:

简短的回答,你不能。 (这不是范围循环的用途。) 您可以做的是将原始范围划分为两个子范围:

    for(auto const& [key, value] : std::ranges::subrange(mymap.begin(), std::prev(mymap.end())) ) {
        std::cout << "key: " << key << ", value: " << value << ", ";
    }
    for(auto const& [key, value] : std::ranges::subrange(std::prev(mymap.end()), mymap.end()) ) {
        std::cout << "key: " << key << ", value: " << value;
    }

https://godbolt.org/z/4EWYjMrqG

或者更多功能:

    for(auto const& [key, value] : std::ranges::views::take(mymap, mymap.size() - 1)) {
        std::cout << "key: " << key << ", value: " << value << ", ";
    }
    for(auto const& [key, value] : std::ranges::views::take(std::ranges::views::reverse(mymap), 1)) {
        std::cout << "key: " << key << ", value: " << value;
    }

或者您可以“索引”范围内的元素。 (STD Ranges 仍然缺少 zip 来执行此操作。)

#include <iostream>
#include <map>
#include <string>

#include <boost/range/adaptor/indexed.hpp>

int main() {
    
    std::map<std::string, size_t> mymap {{"a", 0}, {"b", 1}, {"c", 2}, {"d", 3}};

    for(auto const& [index, kvp] : mymap |  boost::adaptors::indexed(0) ) {
        auto const& [key, value] = kvp;
        if(index != mymap.size() - 1) {std::cout << "key: " << key << ", value: " << value << ", ";}
        else                          {std::cout << "key: " << key << ", value: " << value;}
    }
    return 0;
}

https://godbolt.org/z/f74Pj1Gsz

(令人惊讶的是,Boost.Ranges 与结构化绑定一起工作,就好像它是全新的一样。)

如您所见,重点在于,您通过强迫自己使用 range-for 循环来与语言作斗争。 使用基于迭代器的循环看起来更有吸引力。

【讨论】:

  • 那滴水看起来很贵?就像一个额外的完整容器迭代。
  • @Yakk-AdamNevraumont,是的,也许使用反向?
【解决方案4】:

在已经看到了三种可能的解决方案之后,这里是第四种(诚然相当简单)……

因此,我将分隔符移至输出的开头,并注意在第二次迭代之前对其进行了修改:

#include <iostream>
#include <map>
#include <string>

int main() {
    
    std::map<std::string, size_t> mymap {{"a", 0}, {"b", 1}, {"c", 2}, {"d", 3}};
   
    // using structured bindings, C++17
    const char* sep = "";
    for ( auto const& [key, value] : mymap) {
        std::cout << sep << "key: " << key << ", value: " << value;
        sep = ", ";
    }

    return 0;
}

输出:

key: a, value: 0, key: b, value: 1, key: c, value: 2, key: d, value: 3

Demo on coliru

【讨论】:

  • @alfC 不,一点也不。这是一个指针赋值,并且指针对象(在指针类型中)是 const 是必要的,因为字符串文字 are const.
  • 啊,太好了。您也可以在for 中使用初始化程序:for( const char* sep = ""; auto const&amp; [key, value] : mymap) { ... }
  • @alfC 你也可以在for 中使用初始化器 这似乎从C++20 开始支持。使用 C++17(像我这样的 OP 似乎坚持使用它),它尚不受支持,但变化不大。 ;-)
【解决方案5】:

从 C++20 开始,您可以使用 init 语句并将其编写如下:

#include <iostream>
#include <map>
#include <string>

int main() 
{
    std::map<std::size_t, std::string> map{ {11, "a"}, {13, "b"}, {22, "c"}, {32, "d"} };
    std::size_t end = map.size() - 1;

    for (std::size_t n{ 0 }; auto const& [key, value] : map) 
    {
        std::cout << "key: " << key << ", value: " << value; 
        if ((n++) != end) std::cout << ", ";
    }

    return 0;
}

【讨论】:

    【解决方案6】:

    当我需要位置和数据时,我编写了一个 iterators_of 适配器,它接受一个范围,并返回其迭代器的范围。

    for( auto it:iterators_of(foo) ){
      auto const&[key,value]=*it;
      // blah
      if (std:next(it)!=foo.end())
        std::cout<<',';
    }
    

    iterators of 很短。

    template<class T>
    struct index{
      T t;
      T operator*()const{return t;}
      index& operator++(){++t; return *this;}
      index operator++(int)&{auto self=*this; ++*this; return self;}
      bool operator==(index const&)=default;
      auto operator<=>(index const&)=default;
    };
    

    然后将其扩充为完整的迭代器或直接编写

    template<class It, class Sent=It>
    struct range{
      It b;
      Sent e;
      It begin()const{return b;}
      Sent end()const{return e;}
    };
    template<class R>
    auto iterators_of(R&& r){
      using std::begin; using std::end;
      return range{index{begin(r)},index{end(r)}};
    }
    

    代码不多。

    我发现这比手动使用迭代器迭代更好。

    【讨论】:

    • 很好,iterators_of可以用range::iota实现,看我的新回答。
    猜你喜欢
    • 1970-01-01
    • 2016-02-12
    • 2020-12-05
    • 1970-01-01
    • 2011-08-30
    • 1970-01-01
    • 1970-01-01
    • 2020-11-27
    • 1970-01-01
    相关资源
    最近更新 更多