2

I want to write a module overloading a swap routine, which takes an array and two indices and swaps the two elements around.

This routine should work for a wide range of arrays, so I wanted to overload it:

module mod_swap

    use iso_fortran_env
    implicit none

    interface swap
        module procedure swap_int64, swap_int32, swap_int16, &
            swap_real64, swap_real32
    end interface swap

    public :: swap
    private :: swap_int64, swap_int32, swap_int16, &
        swap_real64, swap_real32

contains

#define VAR_TYPE real
#define TYPE_KIND real64
#include  "swap.inc"
#undef TYPE_KIND

#define TYPE_KIND real32
#include  "swap.inc"
#undef TYPE_KIND
#undef VAR_TYPE

#define VAR_TYPE integer
#define TYPE_KIND int64
#include  "swap.inc"
#undef TYPE_KIND

#define TYPE_KIND int32
#include  "swap.inc"
#undef TYPE_KIND

#define TYPE_KIND int16
#include  "swap.inc"
#undef TYPE_KIND
#undef VAR_TYPE

end module mod_swap

with swap.inc being:

#define PASTER(x,y) x ## _ ## y
#define EVALUATOR(x,y) PASTER(x,y)
#define SWAP EVALUATOR(swap, TYPE_KIND)

    subroutine SWAP(a, i, j)
        implicit none
        VAR_TYPE(kind=TYPE_KIND), intent(inout) :: a(:)
        integer, intent(in) :: i, j
        VAR_TYPE(kind=TYPE_KIND) t
        t = a(i)
        a(i) = a(j)
        a(j) = t
    end subroutine SWAP

When I run gfortran -o test.o -c -cpp test.F90 it fails, and when I run gfortran -E -cpp test.F90 I find out why: the SWAP macro has been expanded to swap ## _ ## int16, not swap_int16 as expected.

However, cpp directly works:

$ cpp test.F90 > test.f90
$ gfortran -c -o test.o test.f90

After browsing this forum and Google in general, I have deduced that the issue is this:

The preprocessor is run in traditional mode.

And indeed, cpp --traditional exhibits the same behaviour as gfortran -E -cpp

So here are my questions:

  1. Is there a better way to implement this routine so that I don't have to repeat the same instructions just because the array type has changed. (Note that the variable t needs to have the same type as a).

  2. Is there a way to make gfortran use the non-traditional preprocessor?

  3. If not, how would I do what I want to do with the traditional preprocessor?

chw21
  • 7,970
  • 1
  • 16
  • 31
  • 1
    Answer to (2) is 'no'. Answer to (3) is to directly use cpp as you have demonstrated. With (3) be prepare for possible odd results as modern C preprocessor syntax and Fortran syntax have conflicts. Answer to (1) probably lies in Fortran's submodule feature. – evets Jun 08 '18 at 05:04
  • 2
    cpp in non-traditional mode will treat `//` as comment delimiters. The traditional mode is used by gfortran because it is necessary to not break the Fortran code. – Vladimir F Героям слава Jun 08 '18 at 08:22

1 Answers1

1
  1. You can implement what you are looking for by using compiler-dependent preprocessor macros. Note that a similar case was already discussed and answered in Concatenating an expanded macro and a word using the Fortran preprocessor. I believe it could be adapted to your case as follows:

    #if defined(__GFORTRAN__) || defined(NAGFOR)
    #define PASTE(a) a
    #define ADD_TRAIL_USCORE(a) PASTE(a)_
    #define CAT(a,b) ADD_TRAIL_USCORE(a)b
    #else
    #define PASTE(a,b) a ## _ ## b
    #define CAT(a,b) PASTE(a,b)
    #endif
    
    #define SWAP CAT(swap,TYPE_KIND)
    

    Note that

    • The above-mentioned answer states that "basically most Fortran compilers (i.e. Intel and PGI) use a relatively normal C-preprocessor with a token pasting operator". My understanding is that the NAG compiler also can't deal with ##, and hence needs to be added to the exception.
    • In the last #define above, there is no space between swap, and TYPE_KIND.
  2. Not that I know of.
  3. As @Vladimir pointed out in the comments, you probably don't want to do that, since it would break the Fortran concatenation operator //.
jme52
  • 1,123
  • 9
  • 18