n={0,1,…,n-1} in C++

孤人 提交于 2020-01-06 06:55:42

问题


The formal definition (in set theory) of a natural number n is as follows:

  • 0 is the empty set
  • 1 = {0}
  • n = {0,1,...,n-1}

I think this would make some C++ code much simpler, if I was allowed to do this:

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

and it printed numbers from 0 to 9.

So I tried doing the following, which doesn't compile:

#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;
}

Any suggestions on how to achieve this? I get the following error with clang++:

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

but I think I should be allowed to do this! :)

Edit: I know I can "fake" it if I add the word "range" (or some other word) in the for loop, but I'm wondering if it's possible to do it without.


回答1:


It can't be done. From section 6.5.4 of the draft of the C++ 14 standard (but C++11 will be very similar)

begin-expr and end-expr are determined as follows:

(1.1) — if _RangeT is an array type, [...];

Well, this one obviously doesn't apply. An int isn't an array

(1.2) — if _RangeT is a class type, [...]

Nope, this doesn't apply either.

(1.3) — otherwise, begin-expr and end-expr are begin(__range) and end(__range), respectively,

Oo! This looks hopeful. You may need to move begin and end into the global namespace, but still...

where begin and end are looked up in the associated namespaces (3.4.2). [ Note: Ordinary unqualified lookup (3.4.1) is not performed. — end note ]

(emphasis mine). Bother! There aren't any namespaces associated with int. Specifically, from section 3.4.2

— If T [int in our case] is a fundamental type, its associated sets of namespaces and classes are both empty.

The only workround is to write a class range which has a suitable begin and end method. Then you could write the very pythonic:

for (int i : range(5))



回答2:


If you look at the cppreference page for range-based for loops, or better yet the relevant section of the standard ([stmt.ranged]p1), you see how it determines the begin_expr to use for the loop. The relevant one for int is

(1.3) otherwise, begin-expr and end-expr are begin(__range) and end(__range), respectively, where begin and end are looked up in the associated namespaces ([basic.lookup.argdep]). [ Note: Ordinary unqualified lookup ([basic.lookup.unqual]) is not performed. — end note ]

(emphasis added)

Unfortunately for use case, for fundamental types such as int, argument-dependent lookup never returns anything.

Instead, what you can do is declare a class to act as the range expression, and give it begin and end methods:

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);
    }
};

Potential improvements to this class include:

  • Making the begin and end methods constexpr (this requires writing your own version of boost::counting_iterator, or getting Boost to make their version constexpr)
  • Adding a user-defined literal option like Range operator""_range
  • Making it work for types other than just unsigned int.

live demo




回答3:


Just for fun...

You have tagged this question C++14 so you can use std::integer_sequence and std::make_integer_sequence.

If the natural number is known compile time (as 10 in your example), you can write a simple constexpr function getArray() (with helper function getArrayH) the get an std::array of values from zero to 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>{}); }

and call it

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

The following is a full working example

#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;
 }



回答4:


You may use some syntax close to what you want, but you would need an array with the numbers through which you want to iterate.

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

Edit: to create the array without declaring each value you may check this question: Is there a compact equivalent to Python range() in C++/STL



来源:https://stackoverflow.com/questions/47020276/n-0-1-n-1-in-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!