Pass a scalar actual argument into an array dummy argument

It certainly works to associate an actual (1 […x 1 x 1…]) array slice (single element) of any rank with a scalar dummy argument already.

But I’m asking for the opposite: allowing a scalar actual argument to be associated with an array dummy argument. I think (but am not 100% sure) a scalar that matches T&K could always be conformal to a (1 […x 1 x 1 …]) for any rank without breaking anything.

But I’m not a compiler writer.

This does not work for an array slice, but it does for an array element. An array slice actual argument would look like array(j:j). An array element would look like array(j). The array slice actual argument will not tkr match a scalar dummy argument. However, both will match an assumed rank dummy argument, but then you usually need select rank blocks within the subprogram to actually use the dummy argument. If there are more than one such dummy arguments, then you typically need to nest the select rank blocks to reference them. It gets complicated very fast.

1 Like

Right. Then the scalar2array() function that I have described above is a simpler solution (and it looks standard conforming, see my edit).

I use all solutions that were listed in the thread - there is no silver bullet. For the sake of completeness, if you want a compile-time optimizable option, there is equivalence :

Program testar

  USE ISO_FORTRAN_ENV, WP=>REAL64, stdin=>INPUT_UNIT

  Implicit NONE
  Real(WP) :: scalar,scalar1(1),array(5)
  equivalence(scalar,scalar1)

  Call inread(stdin, 1, scalar1)
  Call inread(stdin, 5, array)

Contains

  Subroutine inread(iunit, nvars, var)
    Integer,  Intent(IN)    :: iunit, nvars
    Real(WP), Intent(INOUT) :: var(*)
    Read(iunit,*) var(1:nvars)
  End Subroutine inread

End Program testar

Of course the long-term most viable option (for performance, although maybe in I/O from another unit it’s not so relevant) is the interface (don’t forget to write all 15 versions for all supported ranks! lol), can be templated with fypp even if at the price of slightly more complex build.

select rank is nice but it only works on gcc-10+ as far as I remember, so if supporting older compilers is necessary, you may have to rule this out.

2 Likes

Yes, I think this is standard conforming, provided the actual argument has the target attribute.

I use the same trick to alias integer with real scalars and arrays, and in this case I do not think it is standard conforming, but it solves problems that I do not know how to solve any better way. I will not be surprised when, someday, compilers reject this hack, and then I will need to use the more complicated type(*) and assumed rank solutions as discussed in this thread.

I would also note that you used the pointer trick:

real, intent(in), pointer :: s

which is IMO an obscure feature of the language. I usually do this the more obvious way:

real, intent(in), target :: s

which sometimes requires a few more lines of code. In this particular case, it is the same because c_loc(s) is allowed with either declaration. In both cases, the actual argument s must be declared with the target or pointer attribute, which tells the compiler to not create a temporary copy of the scalar argument during the scalar2array(s) argument association. Most codes work even without that declaration, but this is yet another figurative landmine placed within the language waiting to be stepped on by an unwary programmer.

On the contrary, it has the advantage of forcing the caller to declare the actual argument as a TARGET, which it has to be. If the dummy argument is declared as TARGET instead of a POINTER, the compiler won’t complain if TARGET is omitted for the actual argument.

1 Like

Yes, you are right. Ironically, I think this reinforces even more my opinion that this is an obscure feature of the language.

I agree it is somehow. A non pointer actual argument is implicitly casted to a pointer dummy. I was surprised when I learned it, but it’s actually useful in some cases (such as this one). Note that there is a constraint: the dummy POINTER must be INTENT(IN).

For anyone that is curious about the current portability of C_F_POINTER()
and SELECT_RANK() and the proposed pointer rank remapping extension for
Fortran f202y:

Using the compilers available on Compiler Explorer AOCC flang, gfortran,
lfortran, ifx, nvfortran and flang all worked with C_F_POINTER(). AOCC
flang, lfortran, and nvfortran failed with SELECT RANK however; and only
two supported the proposed pointer rank remapping extension for f202y.
gfortran requires a special flag to evoke the extension – perhaps other
compilers do as well(?). Note there is a good chance a more recent
version of lfortran works with the SELECT RANK method.

I thought I had used C_F_POINTER() and SELECT RANK according to the standard both here and GitHub - urbanjost/M_flatten: return a rank one array pointer to a scalar or array of any rank · GitHub but now I need to revisit that.

Fortran source

click to see more...
module m_cast
   use,intrinsic :: iso_fortran_env, only : real32, real64, int32, int64
   use, intrinsic :: iso_c_binding
   implicit none
   private
   public :: singleton ! promote
   interface singleton
      module procedure enlist_int32
      module procedure enlist_int64
      module procedure enlist_real32
      module procedure enlist_real64
   end interface singleton
contains

   function enlist_int32(value) result(p_arr)
      integer(kind=int32),target  :: value
      integer(kind=int32),pointer :: p_arr(:)
      call c_f_pointer( c_loc(value), p_arr, [1] )
   end function enlist_int32

   function enlist_int64(value) result(p_arr)
      integer(kind=int64),target  :: value
      integer(kind=int64),pointer :: p_arr(:)
      call c_f_pointer( c_loc(value), p_arr, [1] )
   end function enlist_int64

   function enlist_real32(value) result(p_arr)
      real(kind=real32),target    :: value
      real(kind=real32),pointer   :: p_arr(:)
      call c_f_pointer( c_loc(value), p_arr, [1] )
   end function enlist_real32

   function enlist_real64(value) result(p_arr)
      real(kind=real64),target    :: value
      real(kind=real64),pointer   :: p_arr(:)
      call c_f_pointer( c_loc(value), p_arr, [1] )
   end function enlist_real64

end module m_cast

program testit
   use M_cast, only : lift=> singleton
   real,allocatable           :: a(:), b(:), c(:)
   real                       :: ra, rb, rc
   character(len=*),parameter :: nl=new_line('A'), g='(*(g0,1x))'
   ! call normally
   A=[1000,2000,3000,4000]
   B=a*10
   C=a*100
   call arrs(a,b,c)
   write(*,g) a, nl, b, nl, c
   ! pass scalar to array arguments using C_F_POINTER()
   ra=1000
   rb=2000
   rc=3000
   call arrs(lift(ra),lift(rb),lift(rc))
   write(*,g)ra, rb, rc

!  Try SELECT_RANK(): The hard way for the called procedure, the easy way for the caller
   ra=10;rb=20;a(:)=30;b(:)=40
   call either(ra,b)
   call either(a,rb)
   write(*,g)ra, rb, a, b

contains

   subroutine arrs(a,b,c)
      real,dimension(:) :: a, b, c
      a=a+1
      b=b+10
      c=c+100
   end subroutine arrs

#ifdef MAPPED
   subroutine either( a, b)
   ! compile with : gfortran xx.f90 -std=f202y -DMAPPED
   ! This technique is known as pointer rank remapping (introduced in
   ! Fortran 2003 and expanded in Fortran 2008).
   ! requires the multi-dimensional target array is simply contiguous.
   real,target, contiguous              :: a(..), b(..)
   integer                              :: i, n
   real,pointer                         :: p_a(:), p_b(:)
      ! NOTE: The assumed rank target is an experimental F202y feature.
      ! NOTE: Explicit bounds are required. That is, you must
      ! specify the explicit upper and lower bounds
      ! on the left-hand side of the pointer assignment.
      n=size(b)
      p_b(1:n)=>b 
      n=size(a)
      p_a(1:n)=>a 
      do i=1,size(p_b)
         p_b(i) = p_b(i)*2
      enddo
      do i=1,size(p_a)
         p_a(i) = p_a(i)*3
      enddo
   end subroutine either
#else
subroutine either( a, b)
   real,target, contiguous  :: a(..), b(..)
   integer                  :: i
   real,pointer             :: p_a(:), p_b(:)
   select rank (b)
   rank (0); p_b=>lift(b)
   rank (1); p_b=>b
   rank (*); print *, 'assumed size is unsupported'; stop 1
   rank default; print *,  'unsupported rank'; stop 2
   end select
   select rank (a)
   rank (0); p_a=>lift(a)
   rank (1); p_a=>a
   rank (*); print *, 'assumed size is unsupported'; stop 1
   rank default; print *,  'unsupported rank'; stop 2
   end select
   do i=1,size(p_b)
      p_b(i) = p_b(i)*2
   enddo
   do i=1,size(p_a)
      p_a(i) = p_a(i)*3
   enddo
end subroutine either
#endif

end program testit

Everyone passed using singleton():

click to see more...
+aoccflang520         :     2	1001.000 2001.000 3001.000 4001.000 
+aoccflang520         :     3	 10010.00 20010.00 30010.00 40010.00 
+aoccflang520         :     4	 100100.0 200100.0 300100.0 400100.0
+aoccflang520         :     5	1001.000 2010.000 3100.000

+flangtrunk           :     2	1001. 2001. 3001. 4001. 
+flangtrunk           :     3	 10010. 20010. 30010. 40010. 
+flangtrunk           :     4	 100100. 200100. 300100. 400100.
+flangtrunk           :     5	1001. 2010. 3100.

+ifxlatest            :     2	1001.000 2001.000 3001.000 4001.000 
+ifxlatest            :     3	 10010.00 20010.00 30010.00 40010.00 
+ifxlatest            :     4	 100100.0 200100.0 300100.0 400100.0
+ifxlatest            :     5	1001.000 2010.000 3100.000

+lfortranlatest       :     2	1001.00000 2001.00000 3001.00000 4001.00000 
+lfortranlatest       :     3	 10010.0000 20010.0000 30010.0000 40010.0000 
+lfortranlatest       :     4	 100100.000 200100.000 300100.000 400100.000
+lfortranlatest       :     5	1001.00000 2010.00000 3100.00000

+nvfortran_x86_26_3   :     2	1001.000 2001.000 3001.000 4001.000 
+nvfortran_x86_26_3   :     3	 10010.00 20010.00 30010.00 40010.00 
+nvfortran_x86_26_3   :     4	 100100.0 200100.0 300100.0 400100.0
+nvfortran_x86_26_3   :     5	1001.000 2010.000 3100.000

+gfortran161          :     2	1001.00000 2001.00000 3001.00000 4001.00000 
+gfortran161          :     3	 10010.0000 20010.0000 30010.0000 40010.0000 
+gfortran161          :     4	 100100.000 200100.000 300100.000 400100.000
+gfortran161          :     5	1001.00000 2010.00000 3100.00000

but adding the EITHER() procedure with SELECT RANK caused AOOC flang, lfortran, and nvfortran to fail

click to see more...
-aoccflang520 SELECT RANK not supported
-aoccflang520         :     2	F90-S-0034-Syntax error at or near identifier rank (/app/example.f90: 103)
-aoccflang520         :    12	F90/x86-64 Linux AOCCAOCC_5.2.0-Build#2035 2026_04_10: compilation completed with severe errors

-lfortranlatest       :     2	; ModuleID = 'LFortran'
-lfortranlatest       :     3	source_filename = "LFortran"
-lfortranlatest       :     4	target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
-lfortranlatest       :     5	
-lfortranlatest       :     6	%string_descriptor = type <{ i8*, i64 }>
-lfortranlatest       :     7	%array.1 = type { float*, i64, i32, i8, i8, i8, i8, i64, [1 x %dimension_descriptor] }
-lfortranlatest       :     8	%dimension_descriptor = type { i64, i64, i64 }
-lfortranlatest       :     9	%array.15 = type { float*, i64, i32, i8, i8, i8, i8, i64, [15 x %dimension_descriptor] }
-lfortranlatest       :    10	
-lfortranlatest       :  7675	code generation error: asr_to_llvm: module failed verification. Error:
-lfortranlatest       :  7676	Stored value type does not match pointer operand type!
-lfortranlatest       :  7677	  store float* %63, %array.1** %p_b, align 8
-lfortranlatest       :  7678	 %array.1*Stored value type does not match pointer operand type!
-lfortranlatest       :  7679	  store float* %109, %array.1** %p_a, align 8
-lfortranlatest       :  7680	 %array.1*
-lfortranlatest       :  7681	
-lfortranlatest       :  7682	
-lfortranlatest       :  7683	Note: Please report unclear, confusing or incorrect messages as bugs at
-lfortranlatest       :  7684	https://github.com/lfortran/lfortran/issues.

-nvfortran_x86_26_3   :     2	NVFORTRAN-S-0034-Syntax error at or near identifier rank (/app/example.f90: 103)
-nvfortran_x86_26_3   :     3	NVFORTRAN-S-0034-Syntax error at or near end of line (/app/example.f90: 104)
-nvfortran_x86_26_3   :     4	NVFORTRAN-S-0034-Syntax error at or near end of line (/app/example.f90: 105)
-nvfortran_x86_26_3   :     5	NVFORTRAN-S-0034-Syntax error at or near ) (/app/example.f90: 106)
-nvfortran_x86_26_3   :     6	NVFORTRAN-S-0034-Syntax error at or near identifier default (/app/example.f90: 107)
-nvfortran_x86_26_3   :     7	NVFORTRAN-S-0034-Syntax error at or near identifier rank (/app/example.f90: 109)
-nvfortran_x86_26_3   :     8	NVFORTRAN-S-0034-Syntax error at or near end of line (/app/example.f90: 110)
-nvfortran_x86_26_3   :     9	NVFORTRAN-S-0034-Syntax error at or near end of line (/app/example.f90: 111)
-nvfortran_x86_26_3   :    10	NVFORTRAN-S-0034-Syntax error at or near ) (/app/example.f90: 112)
-nvfortran_x86_26_3   :    11	NVFORTRAN-S-0034-Syntax error at or near identifier default (/app/example.f90: 113)
-nvfortran_x86_26_3   :    12	NVFORTRAN/x86-64 Linux 26.3-0: compilation completed with severe errors

Only two (AOCC flang and gfortran) passed the proposed assumed rank target (an experimental F202y feature).

click to see more...

-flangtrunk           :     2	error: loc("/app/example.f90":86:7): 'fir.rebox' op box operand must not have unknown rank or type
-flangtrunk           :     3	error: verification of lowering to FIR failed
-ifxlatest            :     2	/app/example.f90(86): error #8793: This assumed-rank variable must not appear in a designator or expression in this context.   [B]
-
-ifxlatest            :     3	      p_b(1:n)=>b 
-ifxlatest            :     4	----------------^
-ifxlatest            :     5	/tmp/ifx1323433118B5EApm/ifxUaUoEw.i90(99): catastrophic error: Too many errors, exiting
-ifxlatest            :     6	compilation aborted for /app/example.f90 (code 1)
-
-lfortranlatest       :     3	Internal Compiler Error: Unhandled exception
-lfortranlatest       :     4	Traceback (most recent call last):
-lfortranlatest       :     5	  Binary file "/cefs/98/98ce3ae339272c6faa61ecc8_consolidated/compilers_fortran_lfortran_0.64.0/bin/lfortran", local address: 0x7577e4
-lfortranlatest       :    17	LCompilersException: Cannot extract the physical type of 2 type.
-
-nvfortran_x86_26_3   :     2	Program terminated with signal SIGSEGV (11)
-
+aoccflang520         :     1	STDOUT:
+aoccflang520         :     2	1001.000 2001.000 3001.000 4001.000 
+aoccflang520         :     3	 10010.00 20010.00 30010.00 40010.00 
+aoccflang520         :     4	 100100.0 200100.0 300100.0 400100.0
+aoccflang520         :     5	1001.000 2010.000 3100.000
+aoccflang520         :     6	10.00000 20.00000 90.00000 90.00000 90.00000 90.00000 80.00000 80.00000 80.00000 80.00000

+gfortran161          :     1    1001.00000 2001.00000 3001.00000 4001.00000 
+gfortran161          :     2     10010.0000 20010.0000 30010.0000 40010.0000 
+gfortran161          :     3     100100.000 200100.000 300100.000 400100.000
+gfortran161          :     4    1001.00000 2010.00000 3100.00000
+gfortran161          :     5    30.0000000 40.0000000 90.0000000 90.0000000 90.0000000 90.0000000 80.0000000 80.0000000 80.0000000 80.0000000
1 Like

@urbanjost The standard requires all the actual arguments to lift/singleton to have the TARGET attribute. Same remark for the examples given for your FLATTEN package.

Without this attribute, the compiler is free to pass a copy of the variable for optimization purposes, which would result in the pointer not pointing to what you want.

That’s where declaring the dummy argument as POINTER instead of TARGET helps: the compiler then enforces the TARGET (or POINTER) attribute for the actual arguments.

Yes, that is another part of the quirkiness of this obscure pointer feature. To new fortran programmers, intent(in) in this situation means that the pointer is not modified within the subprogram, but the data it points to can be modified. There is no way in fortran (that I know of) to also specify that the data is unchanged for a dummy pointer. That might be useful in order to enable some compiler optimizations, but I guess it is a rare enough situation that there has never been a syntax to declare it. It is a little bit like having the parameter and target attributes together, which is also not allowed but might be useful in some situations. In contrast, the intent(in), target attribute for the dummy argument means that the data is not modified within the subprogram. Further, it is not allowed to have both the target and the pointer attribute declared for the dummy argument.

The latest trunk build of lfortran compiles and runs your test case:

$ lfortran --version
LFortran version: 0.66.0-188-g77772c17b-dirty
Status: alpha (expected to fail on third-party codes)
Platform: macOS ARM
LLVM: 23.1.0
Default target: arm64-apple-darwin25.6.0
$ lfortran --cpp --realloc-lhs-arrays selrank.F90
1001.00000 2001.00000 3001.00000 4001.00000 
 10010.0000 20010.0000 30010.0000 40010.0000 
 100100.000 200100.000 300100.000 400100.000
1001.00000 2010.00000 3100.00000
30.0000000 40.0000000 90.0000000 90.0000000 90.0000000 90.0000000 80.0000000 80.0000000 80.0000000 80.0000000
$ 

Gfortran 16 gives the same results:

$ gfortran --version
GNU Fortran (Homebrew GCC 16.2.0) 16.2.0
Copyright (C) 2026 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ gfortran selrank.F90
$ ./a.out
1001.00000 2001.00000 3001.00000 4001.00000 
 10010.0000 20010.0000 30010.0000 40010.0000 
 100100.000 200100.000 300100.000 400100.000
1001.00000 2010.00000 3100.00000
30.0000000 40.0000000 90.0000000 90.0000000 90.0000000 90.0000000 80.0000000 80.0000000 80.0000000 80.0000000
$

Flang:

$ flang --version
Homebrew flang version 23.1.0
Target: arm64-apple-darwin25.6.0
Thread model: posix
InstalledDir: /opt/homebrew/Cellar/flang/23.1.0/bin
Configuration file: /opt/homebrew/Cellar/flang/23.1.0/etc/clang/arm64-apple-darwin25.cfg
$ flang selrank.F90
$ ./a.out
1001. 2001. 3001. 4001. 
 10010. 20010. 30010. 40010. 
 100100. 200100. 300100. 400100.
1001. 2010. 3100.
30. 40. 90. 90. 90. 90. 80. 80. 80. 80.
$

Thanks for checking. Lfortran has been moving to close issues at a breakneck speed but I thought I saw where that would likely be closed on the main trunk but at the moment the most recent version readily available to me is on the Godbolt site.

Nice to see a dusty corner of Fortran running on so many platforms. It seems a good sign that many compllers are up to date and work compatibly with a relatively obscure feature.

I build lfortran directly from their GitHub repository source. On my new MacBook, it only takes about 30 wall clock seconds to build the compiler and its library. The compiler I tested with is was build from a snapshot from yesterday.

Yes, lfortran is progressing very quickly. I just did a git pull, and it looks like @certik is still fixing a few bugs with assumed rank.

1 Like

Yes, I fixed a bunch of bugs regarding assumed rank recently. I am systematically finding/generating new failures and fixing them. It’s still relatively easy to find a bug, so not quite beta yet, but soon!

2 Likes