Static assert in Fortran

Is there a way to do a static assert in Fortran similar to the following in C11 / C23? Preferably, failing the assert should produce a compile time error.

https://en.cppreference.com/w/cpp/language/static_assert

2 Likes

For runtime assert: we have an issue for this at our proposals repository: Add assert statement to Fortran · Issue #70 · j3-fortran/fortran_proposals · GitHub. There is no intrinsic feature like that yet, but you can use C macros to achieve it today.

For compile time assert: I think that is not possible, but it’s an interesting idea. What are some use cases that you have in mind?

Some assertion libraries for Fortran are listed here, but I think they produce run-time errors. Placing procedures in modules, using named constants (parameters) when possible, and specifying argument intents helps to identify problems at compile time, but these practices are not a complete answer to your question.

I’m mainly thinking of cross-compilation scenarios. In this case, I may not be able to execute software in the host environment but I would like to do some sanity checks at compilation.

I see. What kind of checks? Do you want to be executing user functions at compile time?

I’m mainly trying to cross compile HDF5 without having to look up the values via an emulator. They are checking what size reals are available and what C sizes match.

You can assign a negative integer as kind parameter to cause compilation to fail. Here is an example related to detecting quad precision: Compile time detection of extended-double or quad precision - #8 by ivanpribec

Here is an example how you may apply this to storage size:

program assert
use iso_c_binding, only: c_double
integer, parameter :: fortran_dp = kind(1.0d0)
integer, parameter :: wp = merge(fortran_dp, -1, &
    storage_size(1.0_fortran_dp) == storage_size(1.0_c_double))
real(wp) :: foo
foo = 1.0_wp
print *, foo
end program

Thanks to Peter Klausler at Add assert statement to Fortran · Issue #70 · j3-fortran/fortran_proposals · GitHub

$ cat a.f90 
#define STATIC_ASSERT(x) block; real(merge(kind(1.),-1,(x))), parameter :: fail = 1.; end block
!STATIC_ASSERT(1 > 2)
STATIC_ASSERT(sin(0.5) > 0.5)
end
$ gfortran -cpp a.f90
a.f90:3:48:

    3 | STATIC_ASSERT(sin(0.5) > 0.5)
      |                                                1
Error: Kind -1 not supported for type REAL at (1)

So it can be done after all!

2 Likes

Here is @ivanpribec’s example:

#define STATIC_ASSERT(x) block; real(merge(kind(1.),-1,(x))), parameter :: fail = 1.; end block

program assert
use iso_c_binding, only: c_double, c_float
integer, parameter :: fortran_dp = kind(1.0d0)
STATIC_ASSERT(storage_size(1.0_fortran_dp) == storage_size(1.0_c_float))
end program

It would be nice if it were possible to include a message for cases when it is not obvious from the test expression.

Once can (mis-)use the variable name to get a message in the output:

! assert.f90
integer, parameter :: size_check = merge(kind(0), -1, &
    storage_size(1) == storage_size(1.0d0))
integer(size_check) :: int_size_not_equal_to_double
end
$ gfortran assert.f90 
assert.f90:4:19:

    4 | integer(size_check) :: int_size_not_equal_to_double
      |                   1
Error: Kind -1 not supported for type INTEGER at (1)

A variation of Klausler’s macro can be used to include a message:

#define STATIC_ASSERT(x,msg) block; real(kind=merge(kind(1.),-1,(x))), parameter :: fail = len(msg); end block
STATIC_ASSERT(storage_size(1.d0) >= 80,"double precision must be at least 80-bit")
end
$ gfortran -cpp -ffree-line-length-none assert.f90 
assert.f90:2:63:

    2 | STATIC_ASSERT(storage_size(1.d0) >= 80,"double precision must be at least 80-bit")
      |                                                               1
Error: Kind -1 not supported for type REAL at (1)
$ gfortran -fdefault-real-16 -cpp -ffree-line-length-none assert.f90 
$ 

Edit: an alternative version without the len intrinsic would be:

#define STATIC_ASSERT(x,msg) block; character(kind=merge(kind('a'),-1,(x))), parameter :: fail = msg; end block
2 Likes

I wonder if something like this can be included in the Fortran stdlib, considering it relies on a CPP macro :thinking:

@everythingfunctional (cc @gak), is a static assert something that either the planned template or preprocessor facilities could support in a more elegant manner?

I think the compiler should just support it directly.

2 Likes

Static assertions have long been built into Fortran via numbered syntax rules and constraints the violations of which are required by a conforming processor to detect and report e.g.,

   type :: t
   end type
   type(t) :: x, y, z
   z = x + y
end
C:\temp>gfortran -c -ffree-form p.f
p.f:4:7:

    4 |    z = x + y
      |       1
Error: Unexpected derived-type entities in binary intrinsic numeric operator '+' at (1)

A separate static_assert type of clause to be introduced in Fortran, a la C++, should be a last resort I feel. A better option will be to firm up and expand the rules and constraints, as needed for the greater benefit of practitioners.

The “canonical” examples toward static_assert in C++ involve typedefs and working with structs and classes. The link in the original class has one with nocopy objects. By default, a derived type in Fortran is “copiable” to some extent given the intrinsic assignment semantics If practitioners have such specific needs in Fortran, addressing them at the language level will be better in my opinion.

I think we will want something along those lines for templates. E.g. making sure a kind parameter is in fact a valid kind, or a dimension/size parameter is positive, etc. That isn’t an aspect that has made it top of the list yet. It will likely be spelled require some_aspect(...).