Binding C union in Fortran

There are some problems with class(*), allocatable variables, see:

That’s a close description to what it is.


There was a thread recently, which provides a way of emulating a union: Data-pointer-object and unlimited polymorphic target. The solution involves sequence types and class(*), pointer variables. There is still some uncertainty about the conformity of such usage.

Plain union (potentially uncorforming)

program plain_union

use, intrinsic :: iso_fortran_env
implicit none

logical, parameter :: little_endian = ( 1 == transfer( [1_int8, 0_int8], 0_int16) )
integer, parameter :: dp = kind(1.0d0)

type :: t_double ! 8 bytes
   sequence
   real(dp) :: val
end type
type t_int ! 4 bytes
   sequence
   integer :: val
end type

! The data for the union
type(t_double) :: a

type(t_int), pointer :: a_i
type(t_double), pointer :: a_d

class(*), pointer :: u

print *, "little endian? ", little_endian

! z'3FD5 5555 5555 5555' ~= 1/3
a%val = 1.0_dp/3.0_dp
print *, a%val
print '(Z16)', transfer(a,1_int64)

u => a

a_i => u
print '(Z16)', a_i
a_d => u
print '(Z16)', transfer(a_d,1_int64)

end program

Output:

$ gfortran -Wall union.f90 
$ ./a.out
 little endian?  T
  0.33333333333333331     
3FD5555555555555
        55555555
3FD5555555555555
$ flang-new union.f90 
$ ./a.out
 little endian?  T
 .3333333333333333
3FD5555555555555
        55555555
3FD5555555555555
$ ifort union.f90 
$ ./a.out
 little endian?  T
  0.333333333333333     
3FD5555555555555
        55555555
3FD5555555555555
~> nagfor union.f90 
NAG Fortran Compiler Release 7.2(Shin-Urayasu) Build 7203
[NAG Fortran Compiler normal termination]
~> ./a.out
 little endian?  T
   0.3333333333333333
3FD5555555555555
        55555555
3FD5555555555555

All compilers silently accept the type-punning. The only one that doesn’t is nvfortran which says:

NVFORTRAN-S-0155-Illegal POINTER assignment - type mismatch (/app/example.f90: 36)
NVFORTRAN-S-0155-Illegal POINTER assignment - type mismatch (/app/example.f90: 40)
  0 inform,   0 warnings,   2 severes, 0 fatal for plain_union
Compiler returned: 2

Even if the plain union is not conformant due to the type-punning, the tagged case should be legal.

Tagged union

! tagged_union.f90

module menu

implicit none
private

public :: menu_item
public :: pizza_order, burger_order
public :: print_orders

integer, parameter :: ITEM_PIZZA = 1
integer, parameter :: ITEM_BURGER = 2

! N.b. sequence types are not polymorphic

type :: pizza
    sequence
    character(len=1) :: size ! S, M, L
    integer :: toppings ! Number of toppings
end type

type :: burger
    sequence
    integer :: patty_count
    logical :: cheese
end type

! Since item will contain
type :: menu_item
    private
    integer :: type
    class(*), allocatable :: item
end type

contains

    function pizza_order(size,toppings) result(order)
        character(len=1), intent(in) :: size
        integer, intent(in) :: toppings
        type(menu_item) :: order
        if (size /= 'S' .and. size /= 'M' .and. size /= 'L') error stop "Wrong pizza size"
        if (toppings > 5) error stop "Only up to 5 toppings are available"
        order = menu_item(ITEM_PIZZA,pizza(size=size,toppings=toppings))
    end function

    function burger_order(patty_count,with_cheese) result(order)
        integer, intent(in) :: patty_count
        logical, intent(in) :: with_cheese
        type(menu_item) :: order
        order = menu_item(ITEM_BURGER,burger(patty_count,with_cheese))
    end function

    subroutine print_orders(orders)
        type(menu_item), intent(in), target :: orders(:)
        integer :: i

        do i = 1, size(orders)
        select case(orders(i)%type)
        case(ITEM_PIZZA)
            block
                type(pizza), pointer :: p
                p => orders(i)%item
                print '("Pizza: Size ",A1,", ",I0," extra toppings")', &
                    p%size, p%toppings
            end block
        case(ITEM_BURGER)
            block
                type(burger), pointer :: p
                p => orders(i)%item
                write(*,'("Burger: ",I0," patties, ")',advance='no') &
                    p%patty_count
                if (p%cheese) then
                    write(*,'(A)') "with cheese"
                else
                    write(*,'(A)') "no cheese"
                end if
            end block
        case default
            error stop "Unknown menu item"
        end select
        end do
    end subroutine

end module

program tagged_union

use menu
implicit none

type(menu_item), allocatable :: ord(:)

ord = [ pizza_order(size='M',toppings=3), &
        burger_order(patty_count=2,with_cheese=.false.) ]

call print_orders(ord)

end program

Output:

$ flang-new tagged_union.f90 
$ ./a.out
Pizza: Size M, 3 extra toppings
Burger: 2 patties, no cheese
~> nagfor tagged_union.f90 
NAG Fortran Compiler Release 7.2(Shin-Urayasu) Build 7203
[NAG Fortran Compiler normal termination]
~> ./a.out
Pizza: Size M, 3 extra toppings
Burger: 2 patties, no cheese