In F2023, I believe it’s possible to do:
do integer :: i = 1, size(my_array)
...
end do
but I don’t know if any compilers support it yet.
Depending on the elements of the derived type and nature of operations, you could also think of “inverting” the structure of your program, doing the association outside, and using elemental operations inside the association scope:
do i=1,size(my_array)
associate(el => my_array(i))
if (el%y > 5) el%x = el%y + 2
end associate
enddo
associate(x => my_array%x, y => my_array%y)
where(y > 5) x = y + 2
end associate
If the derived type is C-interoperable you can also do the processing in C++, although this increases the complexity of building the code. Borrowing the example of @DavidB:
// range_based_for.cpp
//
#include <iostream>
#include <span> // C++20
#include <cmath>
extern "C" {
struct derived
{
float x;
int i;
};
void range_based_for(int n, derived *a_)
{
std::span<derived> a(a_,n);
for (auto el: a) {
std::cout << std::pow(el.x,el.i) << '\n';
}
}
} // extern "C"
! cpp_example.f90 --
! Demo of calling a C++ routine using range-based for
!
program cpp_example
use, intrinsic :: iso_c_binding
implicit none
type, bind(c) :: derived
real(c_float) :: x = 0.0
integer(c_int) :: i = 2
end type
interface
subroutine range_based_for(n,a) bind(c,name="range_based_for")
import c_int, derived
integer(c_int), value :: n
type(derived) :: a(n)
end subroutine
end interface
type(derived) :: data(10)
integer :: i
! initialize array
do i = 1, size(data)
data(i) = derived(x=real(i))
end do
! perform work in C++
call range_based_for(size(data),data)
end program cpp_example
~/fortran/20240102_range_for$ make run
g++-13 -Wall -std=c++20 -c range_based_for.cpp
gfortran-13 -Wall -o cpp_example cpp_example.f90 range_based_for.o -lstdc++
./cpp_example
1
4
9
16
25
36
49
64
81
100
If we were to do this the “Fortranic” way,
print '(F6.1)', data%x**data%i
the program is shorter than the C++ one-liners:
// range-based for with structured binding
for (auto [x,i]: a) std::cout << std::pow(x,i) << '\n';
// STL "for each"
std::ranges::for_each(a,[](const auto& el){ std::cout << std::pow(el.x,el.i) << '\n'; });
Anyways, besides using elemental
routines, depending on the application, you can also use transformational intrinsic functions (pack
, unpack
, merge
, findloc
), or one of the two-forms of where
statements:
where (mask_expr) y = ...
where (mask_expr)
...
elsewhere
...
end where
An exercise exploring this topic can be found in this thread, where the challenge was to implement a “filter”-type program without explicit do loops.
In modern C++ they rely heavily on range-based for since it solves subtle problems such as,
for (int i = 1; i <= n; i++) ... // off by 1 on lower on upper bounds
for (size_t i = 1; ... ) ... // size_t, unsigned, or int for index variable?
for (...; ++i) ... // i++ or ++i, does it even matter?
In Fortran, missing the bounds can be a problem when using allocatable arrays with custom bounds,
subroutine process_array(a)
real, intent(in), allocatable :: a(:)
do i = 1, size(a) !! potentially wrong
...
end do
do i = lbound(a), ubound(a)
...
end do
end subroutine
But typically you’d use custom arrays bounds for indexing purposes, so I’m not sure a range-based loop syntax would be useful here.
The integer size can be problematic in programs dealing with very large arrays, so that 64-bit integers are needed for indexing. In such cases, it can be painful to remember and consistently use a kind specifier:
subroutine process_array(a)
real, intent(inout) :: a(:) ! a huge array
! covers range -10**12 (exclusive) to 10**12 (exclusive)
integer, parameter :: ip = selected_int_kind(12)
integer(ip) :: i
do i = 1_ip, size(a,ip) ! don't forget the kind!!!
...
end do
end subroutine
With that said, personally I wouldn’t mind having some syntactic sugar for looping over elements. But some good examples of the new syntax (and counter-examples of why the existing options are insufficient) are needed.