【问题标题】:n={0,1,...,n-1} in C++n={0,1,...,n-1} 在 C++ 中
【发布时间】:2018-04-11 17:30:15
【问题描述】:

自然数 n 的正式定义(在集合论中)如下:

  • 0 是空集
  • 1 = {0}
  • n = {0,1,...,n-1}

如果允许我这样做,我认为这会使一些 C++ 代码更简单:

for (int n : 10)
    cout << n << endl;

它打印了从 0 到 9 的数字。

所以我尝试执行以下操作,但无法编译:

#include <iostream>
#include <boost/iterator/counting_iterator.hpp>


    boost::counting_iterator<int> begin(int t)
    {
        return boost::counting_iterator<int>(0);
    }

    boost::counting_iterator<int> end(int t)
    {
        return boost::counting_iterator<int>(t);
    }



int main() 
{
    for (int t : 10)
        std::cout << t << std::endl;

    return 0;
}

关于如何实现这一点的任何建议?使用 clang++ 时出现以下错误:

main.cpp:22:20: error: invalid range expression of type 'int'; no viable 'begin' function available
        for (int t : 10)
                ^ ~~

但我认为我应该被允许这样做! :)

编辑:我知道如果我在 for 循环中添加单词“范围”(或其他单词),我可以“伪造”它,但我想知道是否可以这样做没有。

【问题讨论】:

  • @mathiasfk 它使用了begin(range_expression)end(range_expression),所以即使不是一个好主意也应该可以。
  • std::iota 可能对您有用:en.cppreference.com/w/cpp/algorithm/iota
  • 不过,这是一个不错的、简单的语法。也许是 C++25。
  • using namespace std; is a bad practice,永远不要使用它。
  • @tambre :更不用说将std 模板专门用于此类基本类型是未定义的行为。

标签: c++ c++14 set-theory


【解决方案1】:

这是不可能的。来自draft of the C++ 14 standard 的第 6.5.4 节(但 C++11 将非常相似)

begin-exprend-expr确定如下:

(1.1) — 如果 _RangeT 是数组类型,[...];

嗯,这个显然不适用。 int 不是数组

(1.2) — 如果 _RangeT 是类类型,[...]

不,这也不适用。

(1.3) — 否则,begin-exprend-expr 分别为 begin(__range)end(__range)

哦!这看起来很有希望。您可能需要将 beginend 移动到全局命名空间中,但仍然...

在哪里beginend 在关联的命名空间 (3.4.2) 中查找。 [注:普通不合格查找(3.4.1) 不执行。 ——尾注]

(强调我的)。打扰!没有与 int 关联的任何命名空间。具体来说,从第 3.4.2 节

——如果 T [在我们的例子中是int] 是一个基本类型,那么它关联的命名空间和类集都是空的。

唯一的解决方法是编写一个具有合适的开始和结束方法的类range。然后你可以写出非常pythonic:

for (int i : range(5))

【讨论】:

  • 我认为 range(x) 和 range(x,y) 是更好的语法
  • 击败我,但我保留我的答案,因为它有一个示例 Range 实现。
【解决方案2】:

如果您查看 the cppreference page for range-based for loops,或者更好的是标准的相关部分 ([stmt.ranged]p1),您会看到它如何确定用于循环的 begin_expr。与int 相关的是

(1.3) 否则,begin-exprend-expr 分别为 begin(__range)end(__range),其中 begin 和 end 在关联中查找命名空间 ([basic.lookup.argdep])。 [ 注意:不执行普通的非限定查找 ([basic.lookup.unqual])。 — 尾注 ]

强调添加)

不幸的是,对于用例,对于int 等基本类型,依赖于参数的查找永远不会返回任何内容。

相反,您可以声明一个类作为范围表达式,并为其提供beginend 方法:

struct Range {
    using value_type = unsigned int;
    using iterator = boost::counting_iterator<value_type>;

    unsigned int max;

    iterator begin() const
    {
        return iterator(0);
    }

    iterator end() const
    {
        return iterator(max);
    }
};

对此类的潜在改进包括:

  • 制作beginend 方法constexpr(这需要编写您自己的boost::counting_iterator 版本,或者让Boost 制作他们的版本constexpr
  • 添加用户定义的文字选项,如Range operator""_range
  • 使其适用于 unsigned int 以外的类型。

live demo

【讨论】:

  • 在我看来,C++ 标准草案是此类语言律师的更好来源。
  • @MartinBonner 它们是更好的来源,但在这种情况下,cppreference 通常就足够了,而且它更易于导航,有时更易于阅读。不过,我将添加一个指向标准相关部分的链接。
【解决方案3】:

只是为了好玩...

您已标记此问题 C++14,因此您可以使用 std::integer_sequencestd::make_integer_sequence

如果自然数是已知的编译时间(如您的示例中的10),您可以编写一个简单的constexpr 函数getArray()(使用辅助函数getArrayH)从以下值中获取std::array零到N-1

template <typename T, T ... Vals>
constexpr std::array<T, sizeof...(Vals)>
   getArrayH (std::integer_sequence<T, Vals...> const &)
 { return { { Vals... } }; }

template <typename T, T Val>
constexpr auto getArray ()
 { return getArrayH(std::make_integer_sequence<T, Val>{}); }

并称之为

for ( auto const & i : getArray<int, 10>() )

以下是一个完整的工作示例

#include <array>
#include <utility>
#include <iostream>

template <typename T, T ... Vals>
constexpr std::array<T, sizeof...(Vals)>
   getArrayH (std::integer_sequence<T, Vals...> const &)
 { return { { Vals... } }; }

template <typename T, T Val>
constexpr auto getArray ()
 { return getArrayH(std::make_integer_sequence<T, Val>{}); }

int main ()
 {
   for ( auto const & i : getArray<int, 10>() )
      std::cout << i << std::endl;
 }

【讨论】:

  • 创建整个数组会占用比实际需要更多的内存。如果您的循环边界是510,这不是问题,但对于更大的边界可能是一个大问题。
  • @DanielH - 正如所写,我的回答只是为了好玩;我认为做这类事情的更好方法是使用经典的 for-cycle (for (auto = 0; i &lt; 10 ; ++i).
  • 是的,在大多数情况下我都同意,但在某些情况下,基于范围的解决方案是有意义的,并且那些不需要使用线性内存而不是常量内存。
  • @DanielH - 我同意在这种情况下,基于范围的解决方案是有意义的;但我不认为这是其中之一。
【解决方案4】:

你可以使用一些接近你想要的语法,但你需要一个包含你想要迭代的数字的数组。

int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
for (int n : a)
    std::cout << n << std::endl;

http://en.cppreference.com/w/cpp/language/range-for

编辑:要创建数组而不声明每个值,您可以检查这个问题:Is there a compact equivalent to Python range() in C++/STL

【讨论】:

  • 是的,我已经创建了一个与链接中的非常相似的范围类,但我仍然认为我应该能够使用自然符号,而不必在那里添加单词“范围”。 5 真的是 {0,1,2,3,4},
  • @Miguel 通常自然数由Peano axioms定义。根据这个定义,5 真的是SSSSS0。这些公理有许多不同的模型,包括你的集合论模型。但是,计算机通常使用的是不同的。其中,5“真的是”0b0000'0000'0000'0000'0000'0000'0000'0101(它不是一个完整的模型,因为它不包含大于 2^32-1 或类似值的数字)。
  • 你说得对,我不应该说“5 真的是”。我真的很喜欢从 ZF 的角度来思考。语法不像我建议的那样好看吗?我只是想知道为什么它不可能(或者如果它是,如何)。
  • @Miguel 从实际的角度来看,我认为这是一件好事,这是不可能的,或者至少你不能通过将函数放在 std 中来做到这一点。如果你正在编写一个库,你可能会破坏其他人的代码,或者至少会导致他们认为是编译器错误的令人困惑的错误,如果你让它在任何地方都能工作的话。不过,您可以做的一件事是添加_zf_range 用户定义的文字,这样您就可以说for (i : 5_zf),您可能认为它比for (i : Range(5)) 更好看。
猜你喜欢
  • 2017-09-22
  • 2018-10-21
  • 1970-01-01
  • 1970-01-01
  • 2014-06-11
  • 2022-05-04
  • 2015-10-08
  • 2021-10-06
  • 1970-01-01
相关资源
最近更新 更多