在Fortran中,是否有一种可靠的方法可以获得一个转置后的数组指针?
更新:请看我补充的答案,提出一个“略微可接受”的解决方案
对于一个二维数组,在“变量上下文”中表达式 transpose(x) 是不可接受的,因此你不能把它传给某个例程以以某种方式修改数组。如果你想避免把转置拷贝到临时数组(然后再拷回来)所带来的开销,显然有一个指针仅对数组描述符进行转置(即交换步幅和边界)会很有帮助。但遗憾的是,目前没有直接的语法来实现带有swapped indices的指针关联语句……
有趣的是,编译器(我测试的是gfortran 13.3.0)似乎接受一个实现它的函数调用:
module p_hack
implicit none
contains
function passoc(a) ! pointer function, result accepted in variable context
integer, target :: a(:,:)
integer, pointer :: passoc(:,:)
passoc => a ! just pointing, not yet transposing things
end function
end
program test
use p_hack
integer :: A(2,2) ! NB: not even the target attribute is used!
integer, pointer :: pA(:,:)
pA => passoc( transpose(a) ) !!! This does it !!!
pA(1,:) = [10, 20]
pA(2,:) = [30, 40]
print *, A(1,:) ! indeed printing transpose of the above !
print *, A(2,:)
end
这段输出很好地显示了转置:
10 30
20 40
因此显然指针数组描述符确实以转置的顺序访问了数组,且编译器并未创建临时数组。但我并不认为这在所有情形下都能得到保证。也许对原始数组应用 target 属性会有帮助,但更可能的情况是这根本就不符合标准。问题是:
是否可以用标准兼容的方式实现类似的操作,以获得转置后的指针?
同样值得了解的是,Fortran标准化是否已经在考虑扩展对数组指针的使用可能性。因为与之有些相似的特性,如移位索引,实际上是被标准允许的:
pA(1:2, 4:5) => A ! standard Fortran (if A has target attribute)
pA(3:4, 2:3) => A ! ... and compiler accepts it
NB:对“向下索引指针”也可以提出类似的问题,但在该情形下,标准已经给出了直接的实现方式:
pA(1:2, 2:1:-1) => A ! compile error: "Stride must not be present"
pA => passoc( A(:,2:1:-1) ) !!! This does it, but not needed !!!
pA => A(:,2:1:-1) ! ... because this is already allowed
然而对于转置,似乎没有简单的解决方案。
相关: Returning a Pointer to a Multidimensional Array 是关于C 的,不是Fortran……
解决方案
似乎在某些情况下,获得对“有效参数”的保证指针(防止编译器在输入输出时复制)确实会被标准所支持。在 https://fortran-lang.discourse.group/t/pass-by-reference/5003/7 中我找到了下面这段文字:
If the dummy argument has the TARGET attribute, does not have the VALUE
attribute, and either the effective argument is simply contiguous or the dummy
argument is scalar, assumed-rank, or assumed-shape, and does not have the
CONTIGUOUS attribute, and the effective argument has the TARGET attribute
but is not a coindexed object or an array section with a vector subscript then
* any pointers associated with the effective argument become associated
with the corresponding dummy argument on invocation of the procedure, and
* when execution of the procedure completes, any pointers that do not
become undefined (19.5.2.5) and are associated with the dummy argument
remain associated with the effective argument.
我没有直接链接到标准,但这似乎允许下面给出的一个 ptranspose 函数的(有点初步的)实现,它在变量上下文中是有效的:
module p_hack
implicit none; private; public :: ptranspose
contains
function change_stride(a, dummy1, d1,d1d2) result(p)
integer, pointer :: p(:,:)
integer, intent(inout), target :: a(1:dummy1, 1:d1d2)
integer :: dummy1, d1, d1d2
p => a(1:d1d2:d1, 1:d1) ! super tricky reshape
end
function ptranspose(a)
integer, intent(inout), target :: a(:,:)
integer, pointer :: ptranspose(:,:)
ptranspose => change_stride(a, 1, size(a,1), size(a))
end function
end module
program test
use p_hack
integer, target :: A(3,2) ! has to be contiguous array (generalize?)
integer, pointer :: pA(:,:)
pA => ptranspose( a )
pA(1,:) = [10, 20, 30] !!! Use it as if it is transpose(A) !!!
pA(2,:) = [40, 50, 60]
print *, A(1,:) ! indeed A contains transpose of the above !
print *, A(2,:)
print *, A(3,:)
end
这段会输出所需的结果,表明指针确实指向了A,并且跨越的步幅与边界被重新排列了(当然还可以再做一些测试……)
10 40
20 50
30 60
并且如果需要,可以通过现有的Fortran语法简单地添加边界偏移:
...
pA(0:, 0:) => ptranspose( a )
pA(0,:) = [10, 20, 30] !!! Use it as if it is transpose(A) !!!
pA(1,:) = [40, 50, 60]
print *, A(1,:) ! indeed A contains transpose of the above !
print *, A(2,:)
print *, A(3,:)