当前位置: 首页 > 知识库问答 >
问题:

模拟基于范围的for循环的开始/结束行为

宗政小林
2023-03-14

考虑基于范围的for循环的begin expr和end expr的规范(N4140[标准范围]/p1)。给定类型为RangeT的范围,

开始表达式和结束表达式的确定如下:

  • 如果“范围”是数组类型,则“开始表达式”和“结束表达式”分别是“范围”和“范围绑定”,其中“范围绑定”是数组绑定。如果RangeT是大小未知的数组或类型不完整的数组,则程序格式错误
  • 如果RangeT是类类型,则在类RangeT的范围内查找非限定ID begin和end,就像通过类成员访问查找(3.4.5)一样,如果其中一个(或两个)找到至少一个声明,则begin expr和end expr都是范围。begin()和范围。end()
  • 否则,begin expr和end expr分别是begin(\uu range)end(\uu range),其中beginend在相关名称空间中查找(3.4.2)。[注:未执行普通不合格查找(3.4.1)。-结束注]

有可能在普通C代码中模拟这种精确的行为吗?i、 例如,我们是否可以编写一个magic\u begin和一个magic\u end函数模板,以便

for(auto&& p : range_init) { /* statements */ }

{
    auto&& my_range = range_init;
    for(auto b = magic_begin(my_range), e = magic_end(my_range); b != e; ++b){
        auto&& p = *b;
        /* statements */
    }
}

总是有完全相同的行为?

非应答包括对std::begin的限定调用(不处理第三个项目符号)和使用std::begin的限定调用;开始(范围) begin找到一个与std::begin同样好的重载,这是不明确的。

为了说明,给出

namespace foo {
    struct A { int begin; }; 
    struct B { using end = int; };
    class C { int* begin(); int *end(); }; // inaccessible
    struct D { int* begin(int); int* end();};
    struct E {};

    template<class T> int* begin(T&) { return nullptr; }
    template<class T> int* end(T&) { return nullptr; }
}

foo::A a; foo::B b; foo::C c; foo::D d; foo::E e;

我希望magic\u begin(a)/magic\u begin(b)/magic\u begin(c)/magic\u begin(d)是一个编译错误,并希望magic\u begin(e)返回null ptr(int*)。

共有2个答案

赖渊
2023-03-14

几乎

如果有效,则执行#1,如果无效,则执行#2,如果无效,则执行#3是一个非常基本的标签调度/sfinae练习。

对于#3:

创建一个其他地方不使用的命名空间。将其嵌套在另一个。

在外面,放一个=delete开始函数,它可以接受任何东西。

在其中放置一个调用begin的helper函数。

这将找到adl开始,否则删除的开始。

失败模式是命名空间可以在其他地方使用;没有办法阻止它。

陆运乾
2023-03-14

以下对SFINAE友好的方法似乎能按预期工作(例外情况见下文):

#include <type_traits>

namespace detail {
    struct empty {};
    template <typename T>
    using base = std::conditional_t<std::is_class<T>{} && not std::is_final<T>{},
                                    T, empty>;

    struct P1 {typedef int begin, end;};
    template <typename U>
    struct TestMemType : base<U>, P1 {
        template <typename T=TestMemType, typename=typename T::begin>
        static std::true_type test_begin(int);
        template <typename T=TestMemType, typename=typename T::end>
        static std::true_type test_end(int);

        static std::false_type test_begin(float), test_end(float);
    };

    template <typename T>
    constexpr bool hasMember = !decltype(TestMemType<T>::test_begin(0)){}
                            || !decltype(TestMemType<T>::test_end(0)){};

    //! Step 1
    template <typename T, std::size_t N>
    constexpr auto begin(int, T(&a)[N]) {return a;}
    template <typename T, std::size_t N>
    constexpr auto end(int, T(&a)[N]) {return a+N;}

    //! Step 2 - this overload is less specialized than the above.
    template <typename T>
    constexpr auto begin(int, T& a) -> decltype(a.begin()) {return a.begin();}
    template <typename T>
    constexpr auto end(int, T& a) -> decltype(a.end()) {return a.end();}

    //! Step 3
    namespace nested_detail {
        void begin(), end();
        template <typename T>
        constexpr auto begin_(T& a) -> decltype(begin(a)) {return begin(a);}
        template <typename T>
        constexpr auto end_(T& a) -> decltype(end(a)) {return end(a);}
    }
    template <typename T, typename=std::enable_if_t<not hasMember<std::decay_t<T>>>>
    constexpr auto begin(float, T& a) -> decltype(nested_detail::begin_(a))
    {return nested_detail::begin_(a);}
    template <typename T, typename=std::enable_if_t<not hasMember<std::decay_t<T>>>>
    constexpr auto end(float, T& a) -> decltype(nested_detail::end_(a))
    {return nested_detail::end_(a);}
}

template <typename T>
constexpr auto magic_begin(T& a) -> decltype(detail::begin(0, a))
{return detail::begin(0, a);}
template <typename T>
constexpr auto magic_end  (T& a) -> decltype(detail::end  (0, a))
{return detail::  end(0, a);}

演示。请注意,GCCs查找被中断,因为它不考虑TestMemType::test\u end/begin中的类型名t::begin。可以在此处找到解决方案草图。

第2步中的签入要求类类型是可派生的,这意味着如果这些类或联合具有名称为begin的不可访问成员,则此方法不能正确处理它们。

 类似资料:
  • 由于valgrind中出现了一些分段错误和警告,我发现这段代码不正确,并且在for range循环中有一些悬而未决的引用。 看起来好像开始和结束是从一个临时循环中提取的,并且在循环中丢失了。 当然,一种方法是 然而,我想知道为什么for(auto e:f()[5])是一个错误,以及是否有更好的方法或某种方法来设计f,甚至容器(

  • 在临时范围上基于范围的for循环中,Barry提到以下内容不受被破坏的临时对象的影响,我测试的成员v确实存在于for循环的整个循环中(因为在for循环的整个循环中没有调用析构函数X)。解释是什么?

  • 我对C很陌生,现在就在做中学习。在课堂材料中,我有以下功能: 几分钟前,我像这样使用了普通for循环:

  • 对于我的项目,我需要使循环中的迭代器转到容器中的下一项,执行一些操作,然后再次返回到同一迭代器,然后继续,然而,由于某种原因,和,然后使用似乎都不起作用。那么,我怎样才能得到下一个迭代器,然后返回到上一个迭代器呢? 我收到以下错误消息: 非常感谢。

  • 我在实验基于范围的for循环时发现,如果你使用基于范围的for循环来循环一个向量,它会出现超出范围的错误,如果你使用基于范围的for循环,有没有办法避免这个错误 它会显示此错误

  • 这是从2011年开始对这个问题的扩展:基于范围的for loops和ADL 使用Visual Studio 2015,我无法使用参数依赖查找(ADL)为自定义容器创建基于范围的for循环。 我在下面用一个自定义容器做了一个非常简单的测试用例: 使用此容器和ADL,以下测试可以很好地编译: 这是应该的。我不确定ADL是否在这里得到了利用,但无论如何,它是有意义的。根据MSDN文档,我们有: 请记住以