For element in range in C++
While I was a teacher assistant and during my time helping new employees move from Python to C++, I have gotten one question often.
Why can I not do a for loop like this for elm in range(0, 10)
and you know what that is actually a fair question.
In particular for arithmetic types… but what if you could part of the way?
Well first, let us look at a concept to define a type that must be an arithmetic type.
This can be done fairly easy like this template<typename NumericType> concept Numeric = std::is_arithmetic<NumericType>::value;
.
Basically what this does is to create a type that must be numeric. I will show a little later what happens if you use one that is not.
Next let us generate the range, here I will use std::vector
as my range container.
This can be done very simply with the function below.
This assume that start
is smaller than end
, otherwise it will not work.
template <Numeric T> constexpr std::vector<T> range(T start, T end)
{
T size = end - start;
std::vector<T> data(size);
for (T i = 0; i < size; ++i)
{
data.at(i) = start + i;
}
return data;
}
Now let us test it.
For this we will make a print_range
function which takes start
and end
and calls range
and prints the resulting “range”.
Again we use the Numeric
template type constrained to be a arithmetic type.
template <Numeric T> void print_range(T start, T end)
{
std::cout << "[";
for (const auto val : range(start, end))
{
std::cout << " " << std::to_string(val);
}
std::cout << " ]\n";
}
Finally our main function, where we test with size_t
, int
, and float
.
int main(void)
{
print_range(static_cast<size_t>(1), static_cast<size_t>(10));
print_range(1, 10);
print_range(1.0, 10.0);
}
The expected result is:
[ 1 2 3 4 5 6 7 8 9 ]
[ 1 2 3 4 5 6 7 8 9 ]
[ 1.000000 2.000000 3.000000 4.000000 5.000000 6.000000 7.000000 8.000000 9.000000 ]
The program should be compiled with clang++ -std=c++20
or g++ -std=c++20
.
The full program is listed below:
#include <vector>
#include <concepts>
#include <iostream>
#include <string>
template<typename NumericType> concept Numeric = std::is_arithmetic<NumericType>::value;
template <Numeric T> constexpr std::vector<T> range(T start, T end)
{
T size = end - start;
std::vector<T> data(size);
for (T i = 0; i < size; ++i)
{
data.at(i) = start + i;
}
return data;
}
template <Numeric T> void print_range(T start, T end)
{
std::cout << "[";
for (const auto val : range(start, end))
{
std::cout << " " << std::to_string(val);
}
std::cout << " ]\n";
}
int main(void)
{
print_range(static_cast<size_t>(1), static_cast<size_t>(10));
print_range(1, 10);
print_range(1.0, 10.0);
}
Now let us say we had called print_range
with "a"
as start
and "z"
as end
.
Well then you would get the following compilation error:
main.cpp:34:5: error: no matching function for call to 'print_range'
34 | print_range("a", "z");
| ^~~~~~~~~~~
main.cpp:19:27: note: candidate template ignored: constraints not satisfied [with T = const char *]
19 | template <Numeric T> void print_range(T start, T end)
| ^
main.cpp:19:11: note: because 'const char *' does not satisfy 'Numeric'
19 | template <Numeric T> void print_range(T start, T end)
| ^
main.cpp:6:50: note: because 'std::is_arithmetic<const char *>::value' evaluated to false
6 | template<typename NumericType> concept Numeric = std::is_arithmetic<NumericType>::value;
| ^
1 error generated.
Some final remarks. First,this can be made MUCH prettier (which I may show in a later post). Secondly, this is not very optimised and only serves as inspiration. Finally, play around with it.
./Lars