I am writing a handler to coalesce all attributes of an object into a single declaration. An issue arose with postfix length specifications for character variables. My test program is:
! ****************************************************************************************
! postfix_character_sizes.f90 15-Apr-26 John Collins
! ****************************************************************************************
!
! The Fortran standard allows constructs of the form:
!
! CHARACTER(LEN=8) :: str*16
!
! where str gets a length of 16. The (LEN=8) sets a default for a list.
!
! What can you put in the postfix size?
!
! Integer Bare Paren Param Bare
! Param Param Expr Expr
! gfortran 13.3.0 OK Fails OK OK Fails
! ifx 2025.2.1 20250806 OK OK OK OK Fails
! lfortran 0.51.0, LLVM 19.1.1 OK OK OK OK OK
!
! ****************************************************************************************
PROGRAM postfix_character_size
IMPLICIT NONE
INTEGER,PARAMETER :: p4 = 4
INTEGER,PARAMETER :: p8 = 8
CHARACTER(LEN=20),PARAMETER :: numbers = "12345678901234567890"
! Simple integer, should be OK
CHARACTER(LEN=128) :: str2*2
!
! Simple bare PARAMETER
CHARACTER(LEN=128) :: str4*p4
!
! Parenthesised PARAMETER
CHARACTER(LEN=128) :: str8*(p8)
!
! Parenthesised expression
CHARACTER(LEN=128) :: str12*(p4+p8)
! Un-parenthesised expression
CHARACTER(LEN=128) :: str16*p8+p8
str2 = numbers
str4 = numbers
str8 = numbers
str12 = numbers
str16 = numbers
WRITE(*,'("str2: ",A)')str2
WRITE(*,'("str4: ",A)')str4
WRITE(*,'("str8: ",A)')str8
WRITE(*,'("str12: ",A)')str12
WRITE(*,'("str16: ",A)')str16
END PROGRAM postfix_character_size ! *****************************************************
So: What is supposed to happen? What would we like to happen?
I would say that a bare expression should not be accepted. Of course the asterisk right after the variable name is not a multiplication operator, but the example requires me to do some mental arithmetic to parse it correctly. To be consistent, I would say that a bare parameter also constitutes an expression and should be rejected. In which case gfortran’s behaviour is the only correct one.
Syntactically, R803 (J3/24-007) rule specified a char-length, which is, by R723, either a (type-param-value) or int-literal-constant. The latter must not contain a _ kind-param. The former, by R701 is scalar-int-expr, or *, or :. So, I would say, your *p4 and your *p8+p8 are outside the spec.
I was bothered by this - perhaps you meant that if one wrote:
CHARACTER (LEN=8) :: str1*16, str2
that str1 would have a length of 16 and str2 would be length 8. Both LEN= and *n specify the character length - you’re allowed to have both with the *n taking precedence.
Yes @sblionel, that is exactly right, though I find it ironic that
CHARACTER*8 thing which works on every compiler I use, is non-standard, but
CHARACTER :: thing*8 which doesn’t work everywhere is standard.
From our point of view this is largely academic since we have to process anything which works on the compilers in use, but we can convert the code to something widely accepted.
I think it unfortunate that the rules for expressions differ in different contexts - in arithmetic expressions, data statements, postfix character sizes, the integer arguments to I/O statements… The legacy rules won’t go away any time soon, but I feel that the Standard is moving towards providing more regular alternatives. I welcome this!
and it is not valid Fortran, then we can restrict it. I don’t know why we did it this way, perhaps we reused a function that can handle any expression, but it should actually just be an integer parameter, nothing else, right?
Type parameters participate in type-checking actual arguments and the compiler is obligated to prove that the actual fits (i.e. is “type compatible with”) the dummy (unlike array extents where the compiler is not similarly obligated). This is referred to as “TKR compatibility” and the thing to notice is that Shape is not part of it. Allowing a generic expression would require the compiler to run too much of the program at compile time in order to prove properties. Some reasonable cut had to be devised and that is known as “specification expression”. For anything that doesn’t fit, there is the assumed type-parameter option, *. If the expression ends up some “constant expression”, then you might as well go ahead and call it a kind-type parameter, and the compiler will jump with joy because there is little work to do.
I think the above is not allowed, must be int-literal-constant only, as @sblionel said. But what about this:
str4*(p4) and str16*(p8+p8)
Is that allowed, as long as p4 and p8 are parameters? The standard says char-length is either int-literal-constant or ( type-param-value )?
Separate question for @Jcollins: do you see this character :: a*4 syntax usage a lot in codes? I don’t remember seeing it myself. Is it used in legacy codes a lot?
The :: part is f90, but the variable*4 part is a commonly used f77 feature. The use of parentheses in these declarations is tricky because parentheses are also used to declare arrays. The keyword (len=4) form (since f90) can be used to make it clear to a human, but the compiler must be able to disambiguate the two cases even without it. In most codes I’ve seen, programmers use either the character*4 variable convention or the variable*4 convention. I was unaware that a programmer could use both, with the latter overriding the former.
Happily, fpt has a diagnostic for the situation where the user writes something of the form:
CHARACTER*8 alpha, beta*16, gamma
where the *16 supersedes the *8 after the CHARACTER keyword. (We have this, BTW, because the construct defeated an early version of a compiler widely in use). Checking our library, I found only 349 occurrences, all in a legacy package of 650K lines of fixed format code.
This didn’t trap the case where there is no length specification after the keyword CHARACTER, but a cunning use of grep didn’t find anything in over 5 million lines of code varying from extended Fortran 77 to at least Fortran 2018. It doesn’t happen often.
I don’t think that parentheses will cause a problem because the postfix length specification is preceded by a * character and the array bounds are not.
I was referring there to using parentheses (4) instead of the *4 notation.
Here are some declarations with both character length and array size specified.
character*4 :: a(3)
character :: b*4(3)
character :: c(3)*4
character(4) :: d(3)
character :: e(len=4)(3)
character :: f(3)(len=4)
character :: g(3)(4)
character(4), dimension(3) :: h
Some of these are allowed, and some are not. I sometimes get some of these wrong (e.g. the declaration of b is wrong) when I write codes, while some of the forms are clear to me and I would never get them wrong (e.g. a, d, and h). If I see one like c, I scratch my head and wonder if that is the legal one or if it is instead the b form that is correct. f is not legal, while c is, and I don’t really know why. g is not legal, and I guess I’m sort of glad that it isn’t; that would confuse humans, even if the compiler could always keep it straight.
Referencing a substring of an array element looks like c(2)(3:4) or c(2:3)(3:4), which dates back to f77 for the former and to f90 array syntax for the latter.
These issues could also arise to some extent when declaring arrays of parameterized derived types, but here the allowed declarations are more limited.
type xyz(l)
integer, len :: l
integer :: array(l)
end type xyz
type(xyz(4)) :: i(3)
type(xyz(l=4)), dimension(3) :: j
Here, even without the longer keyword form in the j declaration, I think the meaning of the i declaration is clear to both the compiler and a human.