Your case is very suitable for using a Fortran feature introduced in the 2003 standard (and adopted much later by compilers) named parameterized derived types. First of all, you should check the compliance status of your compiler to know if it's fully supported.
This feature allows you to pass custom parameters when declaring or constructing a derived-type, so internal functionality will be adjusted accordingly. They are good for having different behaviour alternatives grouped in a single type name, possibly with considerable coding or storage differences. There are 2 types of parameters:
kind parameters behave much like the kind specifier of intrinsic types. Kind parameters of all variables must be known at compile time and are treated practically as constant values. The convenience is that you could change it easily (in code time) by altering just a value in the declaration or construction. This is commonly used for specializing the kind of components of intrinsic type.
len parameters behave much like the len specifier of intrinsic character type. You can define len parameters at compile time or runtime, and a len parameter of a variable cannot change unless you declared it allocatable. Moreover, you can have arguments with assumed len-parameters in procedures, and avoid code verbosity. This is commonly used as a "length" or "dimension" parameter for a derived type, because you can use them in the declaration of array bounds and character length.
In general, type parameters are used to mimic the functionality of intrinsic types in derived types, but you could also get "creative" and use it for other things, like the dimension-space of a transformation-matrix type; for a custom "union type" (like an enumeration); as the nature of a physical quantity in a "units of measurement" data-type (a real value annotated with "mass" unity is not directly compatible with a "temperature" one, but they can be handled pretty much the same way in the code); the "arity" of a tuple type...
module mod1
type :: mytype(n)
integer, len :: n
real :: x1
real, dimension(n) :: x2
end type mytype
contains
! your operations here...
end module mod1
And use it like this:
program test_pdt
use mod1
implicit none
type(mytype(10)) :: mt10
type(mytype(1)) :: mt1
integer :: i
mt10%x1 = 40.0
mt10%x2 = [(0.5 * i, i = 1, 10)]
mt1 = mytype(1)(20.0, [30.0])
call sub(mt10)
call sub1(mt1)
contains
subroutine sub(m)
! accepts values with any "n" parameter
type(mytype(*)) :: m
! you can also use them in declarations
integer, dimension(m%n + 1) :: y
type(mytype(m%n)) :: w
print *, m%n, w%n, size(y)
end
subroutine sub1(m)
type(mytype(1)) :: m ! only accepts values with n=1
print *, m%x1, m%x2, m%n
end
end
Warning:
This is feature, despite of having been announced many years ago, was just recently added to most compilers, and you should be aware that there are still some bugs in implementation. You should probably be fine with regular use, but I often face false syntax errors in some corner cases, and even ICE sometimes.