0

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.

Miguel
  • 195
  • 1
  • 7
  • This syntax is used for iterators, from what I understand it's a short for `for(int n = 10.begin(); n != 10.end(); ++n)`. As `10` is a literal it does not offer the needed functions `begin()` and `end()`. – mathiasfk Oct 30 '17 at 16:46
  • Possible duplicate of [Iterator Loop vs index loop](https://stackoverflow.com/questions/14373934/iterator-loop-vs-index-loop) – CodeMonkey123 Oct 30 '17 at 16:56
  • 1
    @mathiasfk It uses `begin(range_expression)` and `end(range_expression)`, so this should be possible even if it’s not a good idea. – Daniel H Oct 30 '17 at 16:58
  • 1
    `std::iota` may be of use to you: http://en.cppreference.com/w/cpp/algorithm/iota – user4581301 Oct 30 '17 at 16:58
  • @mathiasfk: Not quite. You can say `int array[10]; for (int i : array) { ... }`. – Martin Bonner supports Monica Oct 30 '17 at 17:01
  • 1
    That is a nice, simple bit of syntax, though. Maybe C++25. – user4581301 Oct 30 '17 at 17:02
  • 1
    [`using namespace std;` is a bad practice](https://stackoverflow.com/q/1452721/2176813), never use it. – tambre Oct 30 '17 at 17:10
  • 1
    @tambre : Not to mention that specializing `std` templates for fundamental types like this is undefined behaviour. – Martin Bonner supports Monica Oct 30 '17 at 17:19
  • @MartinBonner This isn’t even a specialization; it’s an overload, which I think is never allowed even for user-defined types. That would be easy enough to fix, though; the fact that `int` isn’t user-defined would not. – Daniel H Oct 30 '17 at 18:15

4 Answers4

10

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))
1

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

Daniel H
  • 7,223
  • 2
  • 26
  • 41
  • The drafts of the C++ standard are a better source for this sort of language lawyering in my view. – Martin Bonner supports Monica Oct 30 '17 at 17:21
  • @MartinBonner They are a better source, but in cases like this cppreference is usually good enough, and it’s easier to navigate and sometimes easier to read. I’ll add a link to the relevant section of the standard, though. – Daniel H Oct 30 '17 at 17:25
0

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;
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • 1
    Creating the entire array uses up a lot more memory than is actually necessary. This isn’t an issue if your loop bound is `5` or `10`, but can be a big one for larger bounds. – Daniel H Oct 30 '17 at 17:45
  • 1
    @DanielH - as written, my answer is just for fun; I think the better way to do this sort of things is use the good old classic for-cycle (`for (auto = 0; i < 10 ; ++i)`. – max66 Oct 30 '17 at 18:01
  • Yeah, I agree in most cases, but there are circumstances where a range-based solution would make sense, and those shouldn’t need to use linear instead of constant memory. – Daniel H Oct 30 '17 at 18:03
  • @DanielH - I'm agree that are circumstances where a range-based solution would make sense; but I don't think this is one of those. – max66 Oct 30 '17 at 18:05
-1

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

mathiasfk
  • 1,278
  • 1
  • 19
  • 38
  • Yeah, I had made a range class quite similar to the one in the link, but I still think I should be able to use natural notation and not have to add the word "range" there. 5 really is {0,1,2,3,4}, – Miguel Oct 30 '17 at 17:08
  • 1
    @Miguel Usually natural numbers are *defined* by the [Peano axioms](https://en.wikipedia.org/wiki/Peano_axioms). By that definition, 5 really is `SSSSS0`. There are many different *models* of those axioms, including your set-theory one. The one that computers usually use, though, is different; in it, 5 “really is” `0b0000'0000'0000'0000'0000'0000'0000'0101` (and it isn’t a complete model, because it doesn’t include numbers greater than 2^32-1 or some similar value). – Daniel H Oct 30 '17 at 17:23
  • You are right, I shouldn't have said "5 really is". I just really like to think in terms of ZF. Wouldn't the syntax look nice like I suggested? I was just wondering why it wasn't possible (or if it was, how). – Miguel Oct 30 '17 at 17:27
  • @Miguel From a practical standpoint, I think it’s a good thing it isn’t possible, or at least that you can’t do it by putting the functions in `std`. If you’re writing a library, you could break other people’s code, or at least cause confusing bugs which they would have expected to be compiler errors, if you let this work everywhere. One thing you can do, though, is add an `_zf` or `_range` user-defined literal, so you can say `for (i : 5_zf)`, which you might think looks nicer than `for (i : Range(5))`. – Daniel H Oct 30 '17 at 17:44