std::list<std::reference_wrapper<T>> 的基于概念限制范围的 for 循环

我有一些类 Foo 和一个 std::list<std::reference_wrapper<Foo>>,我想使用基于范围的 for 循环迭代它的元素:

#include <list>
#include <functional>
#include <iostream>


class Foo {
public:
    Foo(int a) : a(a) {}
    int a;
};

int main() {
    std::list<Foo> ls = {{1},{2},{3},{4}};
    std::list<std::reference_wrapper<Foo>> refs(ls.begin(),std::next(ls.begin(),2));
    
    for(auto &foo : refs) {
        std::cout << foo.get().a << std::endl;
    }

    for(Foo &foo : refs) {
        std::cout << foo.a << std::endl;
    }

    return 0;
}

注意用 get() 捕获时额外的 auto,因为我们推断出类型 std::reference_wrapper<Foo>,而在第二种情况下 foo 已经隐式转换为类型 {{1}因为我们明确地抓住了这种类型。

我实际上是在寻找一种方法来捕捉 auto 但隐式地隐式抛弃了 Foo& 以便不必一直在 std::reference_wrapper 中打扰 get() 方法身体,所以我尝试引入一个合适的概念并抓住这个,即我试过

for

并希望它能奏效。 //this is not legal code template<typename T> concept LikeFoo = requires (T t) { { t.a }; }; int main() { std::list<Foo> ls = {{1},2)); for(LikeFoo auto &foo : refs) { std::cout << foo.a << std::endl; } return 0; } 然而将 clang 的类型推导出为 foo,因此实际上下面的代码将是正确的:

std::reference_wrapper<Foo>

然而,//this compiles with clang,but not with gcc template<typename T> concept LikeFoo = requires (T t) { { t.a }; }; int main() { std::list<Foo> ls = {{1},2)); for(LikeFoo auto &foo : refs) { std::cout << foo.get().a << std::endl; } return 0; } 完全拒绝接受基于范围的 for 循环并抱怨 gcc,因为它试图检查 deduced initializer does not satisfy placeholder constraints,这当然评估为假,所以 {{ 1}} 甚至无法捕捉到 LikeFoo<std::reference_wrapper<Foo>> 概念限制。出现两个问题:

  • 哪个编译器是正确的? gcc 应该有效吗?
  • 有没有一种方法可以自动-catch(可能受概念限制)foo,这样就可以避免在{{1}中写入LikeFoo auto& foo : refs }}-循环体?

您可以在 Compiler explorer 找到此示例。

tjdalele 回答:std::list<std::reference_wrapper<T>> 的基于概念限制范围的 for 循环

哪个编译器是正确的? def my_complex_function () # noqa: max-complexity: 13 pass 应该有效吗?

没有。 LikeFoo auto& foo : refsrefs 的范围,因此 reference_wrapper<Foo>& 推导出对 foo 的引用 - 它没有名为 reference_wrapper<Foo> 的成员。约束变量声明不会改变推导的工作方式,它只是有效地表现得像一个额外的 a

有没有一种方法可以自动捕获(可能受概念限制)static_assert,这样就可以避免在 for 循环体中写入 foo : refs

仅仅通过写get()?不可以。但是您可以编写一个范围适配器将 refs 的范围转换为 reference_wrapper<T> 的范围。标准库中已经有这样的东西了,T&:

transform

这是一大口,所以我们可以让它自己命名适配器:

for (auto &foo : refs | std::views::transform([](auto r) -> decltype(auto) { return r.get(); })) {

然后你可以写:

inline constexpr auto unwrap_ref = std::views::transform(
    []<typename T>(std::reference_wrapper<T> ref) -> T& { return ref; });

无论哪种方式,for (auto &foo : refs | unwrap_ref) { ... } for (auto &foo : unwrap_ref(refs)) { ... } 都在这里推导出为 foo

再多做一点工作,您就可以编写一个范围适配器来解包 Foo 但保留任何其他引用类型。

,

这是一个在取消引用时调用 #include <list> #include <functional> #include <iostream> template <typename T> struct reference_wrapper_unpacker { struct iterator { typename T::iterator it; iterator& operator++() { it++; return *this; } iterator& operator--() { it--; return *this; } typename T::value_type::type& operator*() { return it->get(); } bool operator!=(const iterator& other) const { return it != other.it; } }; reference_wrapper_unpacker(T& container) : t(container) {} T& t; iterator begin() const { return {t.begin()}; } iterator end() const { return {t.end()}; } }; class Foo { public: Foo(int a) : a(a) {} int a; }; int main() { std::list<Foo> ls = {{1},{2},{3},{4}}; std::list<std::reference_wrapper<Foo>> refs(ls.begin(),std::next(ls.begin(),2)); for(auto &foo : refs) { std::cout << foo.get().a << std::endl; } for(Foo &foo : refs) { std::cout << foo.a << std::endl; } for(auto &foo : reference_wrapper_unpacker{refs}) { std::cout << foo.a << std::endl; } return 0; } 的包装器的最低限度的工作示例。

$_FILES['yourfile']['size'];

要使其在通用代码中可用,您需要 SFINAE 来检测容器是否确实具有 reference_wrapper,如果没有,只需返回原始容器。

我会忽略那部分,因为它不是原始问题的一部分。

本文链接:https://www.f2er.com/2182.html

大家都在问