I would like to discuss the current methods to manage
templates in fortran, following the thread
which was very interesting, but showed no details on
how to pratically manage the source code.
This feature is very well-known by C++ developers as "templates".
One example of the problems solved by C++ templates is to
have a sorting source code which is able to manage for any
data type, including integers, reals, or even abstract data
types.
I currently know 3 ways of dealing with templates in fortran,
even if none of them is included in any fortran norm
(and none of them is detailed in a fortran book, to my knowledge) :
- pre-processing macros,
- clever use of the "include" statement,
- m4 macros.
Let's begin with the pre-processing macros.
Suppose that the file "sorting_template.f90" contains a template
sorting module, parametrized by the _QSORT_TYPE macro.
The following is an example of parametrized argument declaration :
subroutine qsort ( array, compare )
_QSORT_TYPE, dimension(:) :: array
Here "_QSORT_TYPE" may be an integer, a real or any fortran
derived type.
This name has been chosen because a fortran variable cannot
begin with an underscore so that no confusion can occur
between a preprocessing macro and a variable name.
Before using the template, one must instanciate it,
which is done with :
#define _QSORT_TYPE type(SORT_DATA)
#include "sorting_template.f90"
The "instanciation" is based, for example, on the following
SORT_DATA derived type, which may be defined in the
module "m_testsorting.f90" :
type SORT_DATA
integer key
integer index
end type SORT_DATA
Even if this derived type is quite simple, practical uses of this
method may include more complex data types.
The module "m_testsorting" has to be pre-processed before
being used. After pre-processing, the previous line has
been transformed to :
subroutine qsort ( array, compare )
type(SORT_DATA), dimension(:) :: array
With this method, generic source code templates can be
configured at will and allow to generate specific
sorting algorithms for whatever type of data.
This method can lead to more complex templates, with
no limit in the number of macros, that is, no limit
in the number of abstract data types in the template.
The main drawback is that the debugging process is not
possible interactively. This is because the source code
is generated at compile-time and the "template" does
not correspond to the post-processed one anymore.
One solution is to use manually the pre-processor to
generate the pre-processed template and to debug on that
later file.
Two other methods exist to manage to do generic programming
in fortran : the "include" statement and m4 macros.
The "include" fortran statement can be used so that the
target source code contains the definitions of an
abstract data type used in the template.
This method is used by Arjen Markus in the Flibs
library, for example in the linked list abstract
data structure :
http://flibs.sourceforge.net/linked_list.html
Here we suppose that the file "sorting_template.f90"
contains one sorting subroutine, which arguments
are defined like this :
subroutine qsort_array( array, compare )
type(SORT_DATA), dimension(:) :: array
The trick is to use the include fortran statement
to put that template source code into a context in
which the abstract data type is defined.
The following source code defines a module, the derived
type SORT_DATA and then include the template source code.
module m_testsorting
type SORT_DATA
integer key
integer index
end type SORT_DATA
contains
include "sorting_template.f90"
end module m_testsorting
The module m_testsorting is now providing the derived type
SORT_DATA and the methods to manage it, which are included
in the template. With a little more work, it is even possible
to make so that the final derived type has a name which
corresponds more to the one implemented by the abstraction,
as shown by Arjen Markus on the linkedlist example.
module MYDATA_MODULE
type MYDATA
character(len=20) :: string
end type MYDATA
end module
module MYDATA_LISTS
use MYDATA_MODULE, LIST_DATA => MYDATA
include "linkedlist.f90"
end module MYDATA_LISTS
The main advantage of the "include" method is that
it uses only fortran statements so that the debugging
process is possible interactively. Moreover, it is very
elegant.
Another method is to use m4 macros to generate source code
at compile-time. Gnu m4 is an implementation of the traditional
Unix macro processor. This method is used by Toby White the
in the Fox library :
http://uszla.me.uk/space/software/FoX/
The template source code is defined in files which have the .m4.
extension and the corresponding source code is defined in the
.F90 file. The source code may be difficult to maintain, but the
method is extremely powerful, thanks to the m4 features.
For example, one can use a m4 "for" loop to generate
several subroutine based on one template subroutine.
But, as for the pre-processing method, the interactive
debugging of the .m4 templates is not possible :
instead, the processed .f90 generated files are debugged easily
and directly.
Are there other well-known methods ?
Is there a definitive drawback for one of these methods so
that another way should be chosen?
All comments will be appreciated.
Best regards,
Michaël Baudin
(One remark: part of the code you refer to was written
by Joe Krahn)
In Fortran 2003 you can use the class facilities and unlimited
polymorphic variables to solve these problems, but material
showing how to do that is limited so far.
Regards,
Arjen
Hi.
> Are there other well-known methods ?
Apparently not well-known, but there is:
http://www.macanics.net/forpedo/
Regards,
--
Bil Kleb
http://fun3d.larc.nasa.gov
You can also request a tag sort, where the file byte offeset addresses
of the sorted records are left in the output file (for cases where you
don't have twice the original data size available).
Of course it can't deal with arbitary fields beyond the expected
strings, integers, reals and T/F, nor does it sort variable length
records (but could easily changed to do so, nor does it cater for non-
MS byte order and FP formatting), but I see no need for the formal
route suggested.
I always prefer simple and practical.
At least one text describes an approach equivalent to your pre-
processing approach: Object-Oriented Programming via Fortran 90/95 by
Ed Akin, Cambridge University Press, 2003. I don't have my copy at
hand but I'm pretty sure it's in Chapter 6.
Also, FWIW, some would argue that Fortran 2003 supports at least some
form of generic programming via parametrized derived types (definitely
not the same capability as templates but generic programming
nonetheless) and then there is the class(*) to which Arjen alludes.
Damian
In Fortran 2003 you can use the class facilities and unlimited
polymorphic variables to solve these problems, but material
showing how to do that is limited so far.
--->Would you consider these class facilities and unlimited polymorphic
variables as "object-oriented?"
--
"Shopping for toilets isn't the most fascinating way to spend a Saturday
afternoon. But it beats watching cable news."
~~ Booman
They can certainly be used in that way - for a suitable
definition of "object-oriented". But the main point is
that they give you the possibility to define interfaces
that allow a wide range of actual types.
Regards,
Arjen
I must have a look again at Akin's book - the details
have escaped me ;).
Note that templates in C++ are completely different
beasts than what we are talking about here:
If you define a template A with one parameter T in C++ and
then define in various pieces of your code the same instance,
say a type A< int> in ten different source files,
the compiler will have to create ten different copies of
the actual object code that implements that particular
type.
It is up to the linker to make sure that in the final
executable program there is only one copy (or at least that
there is no conflict between them).
So, in C++ the task is much more complicated than just
generating specific source code from generic source code.
In Fortran we would have to generate the specific source
code for each specific type. And as Michael indicates
there are several methods for that. But in the end,
for the compiler and linker it is just a larger set
of ordinary code!
Regards,
Arjen
I often use Tcl scripts or Fortran programs to
generate the specific code, if other methods
fail - fairly ad hoc, but it works splendidly.
Regards,
Arjen
I have the book in my hands : "Object oriented programming
via fortran 90/95" by Ed Akin.
In fact, I was very enthousiast about the title, but the content
was very disappointing to me.
For me, the single article "Object-based programming in Fortran 90"
by Gray and Roberts is much more interesting, because it gives
the essence of OO :
http://www.ccs.lanl.gov/CCS/CCS-4/pdf/obf90.pdf
About the problem of generic code, here is the only part where the
subject is detailed, in chapter 3, p. 51 :
"None of the OOP languages have all the features one might desire.
For example, the useful concept of <<template>>, which is standard in C
++,
is not in the F90 standard. Yet the author has found that a few
dozen lines of F90 code will define a preprocessor that allows
templates
to be defined in F90 and expanded in line at compile time. [...]"
That is all : I did not find any paragraph showing how to practically
implement templates in fortran, a subject which could have been at
the core of the book with such a title.
The second edition of "Fortran 90/95 explained" does not deal
with that subject neither (but it may be done in the current edition,
I don't know).
Comparing to the methods I allready know, Forpedo seems very
interesting.
The tool is easy to implement within a Makefile, as for m4 macros.
The generic source code really looks like fortran source code,
even with the macros like @T and <T>.
I did the following table to compare the methods
- standard fortran : is the method in the fortran norm
- debug : is the template source code easy to debug
- features : does the method allow to manage complex templates
(this is only my point of view)
Standard Debug Features
Fortran
-----------------------------------------------------------
include * ** *
Pre-processing - * **
m4 - * ****
forpedo - * ***
fortran 2003 * ? ?
Michaël
Keep looking. There is a short passage with more details later in the
book. Again, I'm pretty sure it's chapter 6, but I'll send an exact
page later when I have my copy with me. The reason I found it similar
to what you wrote is your use of the leading underscore, which you
explained was because Fortran variables names cannot start with an
underscore. In his case, he uses a trailing dollar sign ($) for a
similar reason. Look for the string "Template$". The reason I'm so
certain it's there is because I use his technique in my own code. I
learned it from his book and haven't seen it anywhere else.
Damian
Excellent points! Thanks for the clarification.
Damian
Michaël
Hi,
From what I have experienced, after starting to program in Fortran
after 15 years, is that there is very boring to write the same control
logic all over the program, especially when you have to make a minor
change and need to do the same update in many places. Not to metion
the probability to introduce errors by doing so. I also find it boring
to write the logic needed to be able to select a certain algorithm
during program execution.
As I see it, parametrized derived types and CLASS(*) has the intention
to let you do this, but I think that you have to include something
more or extend the these constructs to really have something that
helps.
One idea that I have: Would it not be possible to also parametrize the
procedures?
Something like this
subroutine bbbbb(E,F,H,D)
type,type :: A=(INTEGER,REAL),B=(TypeX,typeY),C
character(10), length :: D=("AlgorithmA")
type(A),allocateable :: E(:)
class(B) :: B
type(C) :: H
.
.
.
end
The compiler could then use the type of E and F, and the value of D,
to select the specific subroutine to use, and I could use D to select
the algorithm to use during program execution.
Regards Gunnar
While I have experienced this code duplication "problem" on rare
occasion, I find that I fail to see that it is a great programming
concern. I don't oppose adding such capabilities to the language, but
please make it a formal part of the language and not some add-on that
will be poorly supported like a pre-processor that largely, probably,
poorly duplicates functionality of a pre-existing native JCL or macro
language or a more popular preprocessor for another language.
<snip>
Regards Gunnar
That really depends on what kind of problems you have to solve.
"If you only have a hammer, everything you see is a nail."
Suppose you have to connect a fortran software
with a Java platform and transfer data between the two with
sockets. The subroutines which are used to manage the send/receive
are complex and have to be able to manage all basic
fortran data types : integer, real, character, and all possible
arrays from rank 1 to rank 7. Let us count 3 * 7 = 21 subroutines
to send data and 21 subroutines to receive data. But you also
have to manage errors (21 more), etc...
In short : the connection is not possible to do in fortran without
fortran templates. The conclusion is : "Aaahh, that fortran language
is crap, let's do it in C++ and the job will be done.".
You think that this case cannot happen ?
Yes it can happen, and it really happened 4 years ago in my last job.
So my colleagues used pre-processing, which is a very good idea,
and that worked.
In the case of Fox, that tool would simply not exist if fortran
templates did not exist (based on m4 in that case).
The fact that "fortran is crap" or that "fortran templates are not
a great concern" depends on what solutions you can put in front of
the problems you have.
Regards,
Michaël Baudin
I've been doing sockets without templates for 15 years. I'm not
familiar enough with your above application comment on it though. I
didn't imply there was no use for templates. I was suggesting that if
we implement them, do it right, not through some half-a$$ preprocessor
method. I oppose preprocessors, because whatever gets put in the
standard will basically be inferior to tools I had on one or another
operating system in the past and I'll always know how inferior it is as
I try to force it to do things it wasn't designed for.
<snip>
>
> Regards,
>
> Michaël Baudin
--
Gary Scott
mailto:garylscott@sbcglobal dot net
Fortran Library: http://www.fortranlib.com
Support the Original G95 Project: http://www.g95.org
-OR-
Support the GNU GFortran Project: http://gcc.gnu.org/fortran/index.html
If you want to do the impossible, don't hire an expert because he knows
it can't be done.
-- Henry Ford
You made a point here, because the pre-processing method is
difficult to debug directly, except if you debug the pre-processed
source code instead of the template one.
This can be easily solved though, via makefile commands such as :
%.f90 : %.F90
$(FPP) $(FPPOPTS) $< -P $@
%.o : %.f90
$(FC) $(F90OPTS) -c $< -o $@
That is to say that the .F90 contains the template to pre-process.
make start to compute the .f90 specific source code, depending
on the template one.
It is only the .f90 pre-processed source code which has to be
compiled. This allows to debug interactively the specific
source code, while maintaining only the template one.
The "-P" option of the fortran preprocessor may allow to remove
the lines introduced by the preprocessor in the preprocessed
file which points to the line number in the original template.
Now that I have revealed all my very secret tricks (!!!), I don't
see any main drawback to using pre-processing as a template method,
except that the features implemented in many pre-processor are very
poor, compared to what is possible in Python, m4 or Tcl for example.
Most compilers include a pre-processor inside the
compiler itself, for example Intel Fortran, gfortran, g95, Sun,
Ibm, etc...
Even if the pre-processor is not included in
your favorite compiler, or if you dislike the features
that it provides, you can use an external one, for example
the C pre-processor. If the C pre-processor is not available
on the machine (which has a very low probability, but is possible),
you can get the source of gcc and compile your own release.
In fact, the main reason why pre-processing macros are not used
widely in fortran is for historical reasons, I suppose, and by the
fact
that many fortran developers are developing only in fortran,
with high-level skills in science, most of the time, and, most
of the time, low-level skills in software : there are many,
many exceptions to this general fact, and these exceptions are
the core of this forum.
Put a C developer in front of a fortran source, and he will
introduce a new pre-processing macro every 3 minutes...
What I am curious is at the fortran 2003 features which
allows to do this. I think I'm going to read the standard
again.
Best regards,
Michaël
> Suppose you have to connect a fortran software
> with a Java platform and transfer data between the two with
> sockets. The subroutines which are used to manage the send/receive
> are complex and have to be able to manage all basic
> fortran data types : integer, real, character, and all possible
> arrays from rank 1 to rank 7. Let us count 3 * 7 = 21 subroutines
> to send data and 21 subroutines to receive data. But you also
> have to manage errors (21 more), etc...
> In short : the connection is not possible to do in fortran without
> fortran templates.
I agree that something like templates can be useful in some situations.
But note that, depending on details, the particular scenario described
above can usually be handled better in other ways. Typically, all you
need is an equivalent of a C void pointer, along with the data size.
Then you can do it all in a single procedure, with no need for
templates. I've done this exact thing for a long time, as I also have
Fortran code that passes data via sockets. So have other people. This
one is done a lot.
You end up with a single procedure, rather than the multiplicity of
procedures that the template approach involves. The template approach
makes it more practical to create that multiplicity of procedures, but
they still do end up having to get created.
In Fortran 90/95, you have to use tricks that aren't technically
standard conforming, but still have decent portability in practice, in
order to do the needed "type cheating". In Fortran 2003, you can use
either the C interop features or the unlimitted polymorphic feature to
make such code standard conforming.
--
Richard Maine | Good judgement comes from experience;
email: last name at domain . net | experience comes from bad judgement.
domain: summertriangle | -- Mark Twain
I don't understand how to implement this way. What kind of fortran
data type corresponds to a C void pointer ?
Let's take a practical example or reading a data in a string.
The following source code read a data in an internal file
and returns the value read, depending on the type of variable.
It is over-simplified to make the point shorter.
It does not use fortran templates, but only standard fortran.
interface readfile_data
module procedure readfile_data_logical
module procedure readfile_data_integer
end interface readfile_data
subroutine readfile_data_logical ( current_data_line , myvalue ,
status )
implicit none
character ( len = 200 ) , intent(in) :: current_data_line
logical , intent (out) :: myvalue
integer, intent(out) :: status
status = 0
read ( current_data_line , * , err = 100 , end = 100) myvalue
return
100 continue
status = 1
end subroutine readfile_data_logical
subroutine readfile_data_integer ( current_data_line , myvalue ,
status )
implicit none
character ( len = 200 ) , intent(in) :: current_data_line
integer , intent (out) :: myvalue
integer, intent(out) :: status
status = 0
read ( current_data_line , * , err = 100 , end = 100) myvalue
return
100 continue
status = 1
end subroutine readfile_data_integer
It is very important to factorise this small code, because the
interface
could be extended way beyond the current state, for example reading
more
complex data types :
interface readfile_data
module procedure readfile_data_logical
module procedure readfile_data_integer
module procedure readfile_data_real
module procedure readfile_data_double
module procedure readfile_data_logical_array1
module procedure readfile_data_integer_array1
module procedure readfile_data_real_array1
module procedure readfile_data_double_array1
module procedure readfile_data_logical_array2
module procedure readfile_data_integer_array2
module procedure readfile_data_real_array2
module procedure readfile_data_double_array2
[etc... until the maximum rank size available in fortran is reached
= 7]
end interface readfile_data
with the following header for the readfile_data_double_array2
subroutine :
subroutine readfile_data_double_array2 ( current_data_line ,
myvalue , status )
implicit none
character ( len = 200 ) , intent(in) :: current_data_line
real(dp), dimension(:,:) , intent (out) :: myvalue
integer, intent(out) :: status
Developing the code with standard fortran is near to impossible,
although hiring a PhD with European funds to do this is allways
possible...
With fortran templates, it is really easy.
Suppose that the file "readfile_data_template.F90" contains the
following
source code, parametrized with the "_DATATYPE" macro.
subroutine readfile_data__DATATYPE ( current_data_line , myvalue ,
status )
implicit none
character ( len = 200 ) , intent(in) :: current_data_line
_DATATYPE , intent (out) :: myvalue
integer, intent(out) :: status
status = 0
read ( current_data_line , * , err = 100 , end = 100) myvalue
return
100 continue
status = 1
end subroutine readfile_data_integer
The previous source code would be using the template like this :
interface readfile_data
module procedure readfile_data_logical
module procedure readfile_data_integer
end interface readfile_data
#define _DATATYPE logical
#include "readfile_data_template.F90"
#define _DATATYPE integer
#include "readfile_data_template.F90"
And now, with only 6 lines of source code, extending it to
manage for several data types, and arrays of all possible
sizes is possible. The same problem would be even easier to
manage with Forpedo, I suppose.
Now, how do you suggest to solve the problem with your method
Richard ? What is the fortran type which corresponds to the
C void pointer ?
Regards,
Michaël
subroutine _READFILE_DATA_NAME ( current_data_line , myvalue ,
status )
implicit none
character ( len = * ) , intent(in) :: current_data_line
_DATATYPE , intent (out) :: myvalue
integer, intent(out) :: status
status = 0
read ( current_data_line , * , err = 100 , end = 100) myvalue
return
100 continue
status = 1
end subroutine _READFILE_DATA_NAME
And this is the test file :
module m_moduletest2
interface readfile_data
module procedure readfile_data_logical
module procedure readfile_data_integer
end interface readfile_data
contains
#define _DATATYPE logical
#define _READFILE_DATA_NAME readfile_data_logical
#include "test2_template.F90"
#undef _DATATYPE
#undef _READFILE_DATA_NAME
#define _DATATYPE integer
#define _READFILE_DATA_NAME readfile_data_integer
#include "test2_template.F90"
#undef _DATATYPE
#undef _READFILE_DATA_NAME
end module m_moduletest2
program test2
use m_moduletest2
character (len=200) :: current_data_line
integer :: myintvalue1
logical :: mylogicalvalue1
integer :: status
current_data_line = "1"
call readfile_data ( current_data_line , myintvalue1 , status )
write ( * , * ) "status:", status
write ( * , * ) "Integer : ", myintvalue1
current_data_line = ".true."
call readfile_data ( current_data_line , mylogicalvalue1 , status )
write ( * , * ) "status:", status
write ( * , * ) "Logical : ", mylogicalvalue1
end program test2
I tried to use "include" only statements without pre-processing,
and that lead to a source code which I am not proud of.
But the "C void pointer" idea leads to the following source.
The "void" idea is managed with a derived type containing all
possible fortran basic data types.
The "pointer" is the class name, a string containing "integer",
or "logical", depending on the type to manage.
This is the source code :
module m_moduletest4
type :: DATATYPE
integer :: value_integer
logical :: value_logical
character ( len = 200 ) :: classname
end type DATATYPE
contains
subroutine readfile_data ( current_data_line , classname , myvalue ,
status )
implicit none
character ( len = * ) , intent(in) :: current_data_line
character ( len = * ) , intent(in) :: classname
type ( DATATYPE ) , intent(out) :: myvalue
integer, intent(out) :: status
status = 0
myvalue % classname = classname
select case ( classname )
case ( "integer" )
read ( current_data_line , * , err = 100 , end = 100) myvalue %
value_integer
case ( "logical" )
read ( current_data_line , * , err = 100 , end = 100) myvalue %
value_logical
case default
write(6,*) "Bad classname."
end select
return
100 continue
status = 1
end subroutine readfile_data
subroutine printdata ( myvalue )
implicit none
type ( DATATYPE ) , intent(in) :: myvalue
write ( * , * ) trim(myvalue % classname) , ":"
select case ( myvalue % classname )
case ( "integer" )
write ( * , * ) myvalue % value_integer
case ( "logical" )
write ( * , * ) myvalue % value_logical
case default
write(6,*) "Bad classname."
end select
end subroutine printdata
end module m_moduletest4
program test4
use m_moduletest4
character (len=200) :: current_data_line
type ( DATATYPE ) :: myvalue
integer :: status
current_data_line = "1"
call readfile_data ( current_data_line , "integer", myvalue ,
status )
write ( * , * ) "status:", status
call printdata ( myvalue )
current_data_line = ".true."
call readfile_data ( current_data_line , "logical" , myvalue ,
status )
write ( * , * ) "status:", status
call printdata ( myvalue )
end program test4
Of course, dealing with that implementation consumes a lot of memory,
especially if we include arrays of integers, arrays of logicals,
etc...
To solve that, we can use fortran 90 pointers and allocate only the
type under use. Since the abstract data type is more complex, it
is now time for full OO.
module m_moduletest5
type :: DATATYPE
integer, pointer :: value_integer => NULL()
logical, pointer :: value_logical => NULL()
end type DATATYPE
contains
subroutine data_newfromstring ( this , current_data_line ,
classname , status )
implicit none
character ( len = * ) , intent(in) :: current_data_line
character ( len = * ) , intent(in) :: classname
type ( DATATYPE ) , intent(out) :: this
integer, intent(out) :: status
status = 0
select case ( classname )
case ( "integer" )
allocate ( this % value_integer )
read ( current_data_line , * , err = 100 , end = 100) this %
value_integer
case ( "logical" )
allocate ( this % value_logical )
read ( current_data_line , * , err = 100 , end = 100) this %
value_logical
case default
write(6,*) "Bad classname."
end select
return
100 continue
status = 1
end subroutine data_newfromstring
subroutine data_print ( this )
implicit none
type ( DATATYPE ) , intent(in) :: this
if ( associated ( this % value_integer ) ) then
write ( *, * ) "integer :"
write ( * , * ) this % value_integer
elseif ( associated ( this % value_logical ) ) then
write ( *, * ) "logical :"
write ( * , * ) this % value_logical
endif
end subroutine data_print
subroutine data_free ( this )
implicit none
type ( DATATYPE ) , intent(in) :: this
if ( associated ( this % value_integer ) ) then
deallocate ( this % value_integer )
elseif ( associated ( this % value_logical ) ) then
deallocate ( this % value_logical )
endif
end subroutine data_free
end module m_moduletest5
program test5
use m_moduletest5
character (len=200) :: current_data_line
type ( DATATYPE ) :: myvalue
integer :: status
current_data_line = "1"
call data_newfromstring ( myvalue , current_data_line , "integer",
status )
write ( * , * ) "status:", status
call data_print ( myvalue )
call data_free ( myvalue )
current_data_line = ".true."
call data_newfromstring ( myvalue , current_data_line , "logical" ,
status )
write ( * , * ) "status:", status
call data_print ( myvalue )
call data_free ( myvalue )
end program test5
I admit that the current code is still manageable, but have many
limitations
that the pre-processing version have not, including :
- the memory is managed at hand, which can lead to memory leaks.
This is easy to manage with only 2 basic types, but what if there are
21 ?
- the abstract data type cannot handle user-defined derived-types.
The pre-processing system allows to define whatever type you want to,
without any complication in the client source code.
This is not the case with the hand-crafted "pointer to everything"
abstract data type.
On the good side, the source code is very easy to debug and uses only
standard fortran 90 statements.
Still the current "pointer to everything" is a heavy solution.
What if we wand to define a "writetostring" method : another 21*3
block of source code. And what if we want to define an error
system for the class : another 21*3 block of source code !
This is not practical.
But you may suggest another way ?
Best regards,
Michaël
> What kind of fortran
> data type corresponds to a C void pointer ?
Type(C_PTR) in the F2003 C interop stuff.
> end module m_moduletest2
OK, how about:
C:\gfortran\clf\template_war>type test3_template.i90
private
public readfile_data
interface readfile_data
module procedure READFILE_DATA_NAME
end interface readfile_data
contains
subroutine READFILE_DATA_NAME(current_data_line,Qmyvalue,status)
character(len=*), intent(in) :: current_data_line
intent (out) :: Qmyvalue
integer, intent(out) :: status
status = 0
read(current_data_line, *, err=100 , end=100) Qmyvalue
return
100 continue
status = 1
end subroutine READFILE_DATA_NAME
C:\gfortran\clf\template_war>type test3.f90
module logical_mod
implicit logical (Q)
include 'test3_template.i90'
end module logical_mod
module integer_mod
implicit integer (Q)
include 'test3_template.i90'
end module integer_mod
module m_moduletest3
use logical_mod
use integer_mod
end module m_moduletest3
program test3
use m_moduletest3
character (len=200) :: current_data_line
integer :: myintvalue1
logical :: mylogicalvalue1
integer :: status
current_data_line = "1"
call readfile_data(current_data_line, myintvalue1, status)
write(*, *) "status:", status
write(*, *) "Integer : ", myintvalue1
current_data_line = ".true."
call readfile_data(current_data_line, mylogicalvalue1, status )
write(*, *) "status:", status
write(*, *) "Logical : ", mylogicalvalue1
end program test3
C:\gfortran\clf\template_war>C:\gfortran\win64\bin\x86_64-pc-mingw32-gfortran
te
st3.f90 -otest3
C:\gfortran\clf\template_war>test3
status: 0
Integer : 1
status: 0
Logical : T
Isn't this an exercise in shooting fish in a barrel?
C:\gfortran\clf\template_war>type test6.f90
module m_moduletest6
use ISO_C_BINDING, only: C_PTR, C_LOC, C_F_POINTER
implicit none
private C_PTR, C_LOC, C_F_POINTER
type :: DATATYPE
type(C_PTR) value
character ( len = 200 ) :: classname
end type DATATYPE
contains
subroutine readfile_data(current_data_line,classname,myvalue,status)
implicit none
character(len=*) , intent(in) :: current_data_line
character(len=*) , intent(in) :: classname
type(DATATYPE), intent(out) :: myvalue
integer, intent(out) :: status
integer, pointer :: pi4
logical, pointer :: pL4
status = 0
myvalue%classname = classname
select case(classname)
case ("integer")
allocate(pi4)
read(current_data_line, *, err=101, end=101) pi4
myvalue%value = C_LOC(pi4)
return
101 deallocate(pi4)
case("logical")
allocate(pL4)
read(current_data_line, *, err=102, end=102) pL4
myvalue%value = C_LOC(pL4)
return
102 deallocate(pL4)
case default
write(6,*) "Bad classname."
end select
return
100 continue
status = 1
end subroutine readfile_data
subroutine printdata(myvalue)
implicit none
type(DATATYPE), intent(in) :: myvalue
integer, pointer :: pi4
logical, pointer :: pL4
write(* ,*) trim(myvalue%classname), ":"
select case(myvalue%classname)
case("integer")
call C_F_POINTER(myvalue%value, pi4)
write(* ,*) pi4
case("logical")
call C_F_POINTER(myvalue%value, pL4)
write(* ,*) pL4
case default
write(6,*) "Bad classname."
end select
end subroutine printdata
end module m_moduletest6
program test6
use m_moduletest6
character (len=200) :: current_data_line
type(DATATYPE) :: myvalue
integer :: status
current_data_line = "1"
call readfile_data(current_data_line, "integer", myvalue, status)
write(*, *) "status:", status
call printdata(myvalue)
current_data_line = ".true."
call readfile_data(current_data_line, "logical", myvalue, status)
write(* ,*) "status:", status
call printdata(myvalue)
end program test6
C:\gfortran\clf\template_war>C:\gfortran\win64\bin\x86_64-pc-mingw32-gfortran
te
st6.f90 -otest6
C:\gfortran\clf\template_war>test6
status: 0
integer:
1
status: 0
logical:
T
> - the abstract data type cannot handle user-defined derived-types.
> The pre-processing system allows to define whatever type you want to,
> without any complication in the client source code.
> This is not the case with the hand-crafted "pointer to everything"
> abstract data type.
Not the case, see my examples above.
> But you may suggest another way ?
Naturally.
--
write(*,*) transfer((/17.392111325966148d0,6.5794487871554595D-85, &
6.0134700243160014d-154/),(/'x'/)); end
> One example of the problems solved by C++ templates is to
> have a sorting source code which is able to manage for any
> data type, including integers, reals, or even abstract data
> types.
>
> I currently know 3 ways of dealing with templates in fortran,
> even if none of them is included in any fortran norm
> (and none of them is detailed in a fortran book, to my knowledge) :
> - pre-processing macros,
> - clever use of the "include" statement,
> - m4 macros.
You forgot "callbacks":
! Fortran 95 implementation of the quicksort algorithm. This
! subroutine does not directly touch the array it sorts; rather it
! relies on the two callbacks "compare" and "exchange" for that. Code
! inspired by R. Sedgewick, "Algorithms in C" and correspondence with
! Glen Herrmannsfeldt.
subroutine quicksort(n, compare, exchange)
implicit none
integer, intent(in) :: n ! the length of the implied array
! The compare function must return an integer that is
! greater than zero if element(i) > element(j)
! equal to zero if element(i) = element(j)
! less than zero if element(i) < element(j)
interface
integer function compare(i,j)
integer, intent(in) :: i, j
end function compare
end interface
! The exchange subroutine exchanges the elements at locations i and j.
interface
subroutine exchange(i,j)
integer, intent(in) :: i, j
end subroutine exchange
end interface
integer, dimension(2,n) :: stack
integer :: sptr, left, right, pivot
sptr = 1
call push(1, n)
do while(pop(left, right))
if (left .ge. right) cycle
pivot = partition(left, right)
if (pivot .gt. (left+right)/2) then
call push(left,pivot-1)
call push(pivot+1,right)
else
call push(pivot+1,right)
call push(left,pivot-1)
end if
end do
return
contains
subroutine push(l, r)
integer, intent(in) :: l, r
stack(1, sptr) = l
stack(2, sptr) = r
sptr = sptr + 1
end subroutine push
logical function pop(l, r)
integer, intent(out) :: l, r
if (sptr .gt. 1) then
sptr = sptr - 1
l = stack(1, sptr)
r = stack(2, sptr)
pop = .true.
else
pop = .false.
end if
end function pop
integer function partition(l,r)
integer, intent(in) :: l, r
integer :: i, j
i = l
j = r - 1
do
do while (compare(i,r) .lt. 0)
i = i+1
end do
do while (compare(j,r) .gt. 0)
if (j .eq. l) exit
j = j-1
end do
if (i .ge. j) exit
call exchange(i,j)
end do
call exchange(i,r)
partition = i
end function partition
end subroutine quicksort
Chip
--
Charles M. "Chip" Coldwell
"Turn on, log in, tune out"
GPG Key ID: 852E052F
GPG Key Fingerprint: 77E5 2B51 4907 F08A 7E92 DE80 AFA9 9A8F 852E 052F
I must say that the method based on implicit statements is
very clever, and, based only on fortran statements, allows
to minimize the code duplication. But is it possible to
declare something like this :
module data_mod
type MYDATA
character(len=20) :: string
end type MYDATA
implicit type(MYDATA) (Q)
include 'test3_template.i90'
end module data_mod
I don't think so, which shows that the method cannot be extended
to abstract data types. But funny though.
The method based on "ISO_C_BINDING, only: C_PTR, C_LOC, C_F_POINTER"
is interesting but leads to code duplication.
If I where to use "ISO_C_BINDING", I think that I would use the
C++ templates, and only define in fortran the interface to the
C++ source code.
All in all, it would be much more simpler if the fortran language
include a "template" feature in the core, which may be done,
in 2043, may be...
Michaël
> I must say that the method based on implicit statements is
> very clever, and, based only on fortran statements, allows
> to minimize the code duplication. But is it possible to
> declare something like this :
>
> module data_mod
> type MYDATA
> character(len=20) :: string
> end type MYDATA
> implicit type(MYDATA) (Q)
> include 'test3_template.i90'
> end module data_mod
>
> I don't think so, which shows that the method cannot be extended
> to abstract data types.
Yes, it is possible to do something like that. The only problem with the
above is one of ordering. Implicit statements have to come quite early
in a scoping unit, before pretty much anything other than USE
statements. I forget whether it would be ok to just swap the order or
whether a different ordering constraint prevents that. If that doesn't
work, defining the derived type in a separate module, which you USE
here, would work. Derived types are allowed in implicit statements, I'm
sure.
However, I can't really recommend that approach. The well-known
error-proneness of implicit typing is greatly magnified in the presence
of derived types, modules, and host association.
> I must say that the method based on implicit statements is
> very clever, and, based only on fortran statements, allows
> to minimize the code duplication. But is it possible to
> declare something like this :
> module data_mod
> type MYDATA
> character(len=20) :: string
> end type MYDATA
> implicit type(MYDATA) (Q)
> include 'test3_template.i90'
> end module data_mod
> I don't think so, which shows that the method cannot be extended
> to abstract data types. But funny though.
The method is indeed clever. Wish I had thought of it myself,
but someone else suggested it in clf.
As Richard pointed out of course you can do this:
C:\gfortran\clf\template_war>type test3_template.i90
private
public readfile_data
interface readfile_data
module procedure READFILE_DATA_NAME
end interface readfile_data
contains
subroutine READFILE_DATA_NAME(current_data_line,Qmyvalue,status)
character(len=*), intent(in) :: current_data_line
intent (out) :: Qmyvalue
integer, intent(out) :: status
status = 0
read(current_data_line, *, err=100 , end=100) Qmyvalue
return
100 continue
status = 1
end subroutine READFILE_DATA_NAME
C:\gfortran\clf\template_war>type test7.f90
module data_mod
implicit none
type MYDATA
character(len=20) string
end type MYDATA
end module data_mod
module logical_mod
implicit logical (Q)
include 'test3_template.i90'
end module logical_mod
module integer_mod
implicit integer (Q)
include 'test3_template.i90'
end module integer_mod
module MYDATA_mod
use data_mod
implicit type(MYDATA) (Q)
include 'test3_template.i90'
end module MYDATA_mod
module m_moduletest3
use logical_mod
use integer_mod
use MYDATA_mod
end module m_moduletest3
program test3
use data_mod
use m_moduletest3
character (len=200) :: current_data_line
integer :: myintvalue1
logical :: mylogicalvalue1
type(MYDATA) :: myMYDATAvalue1
integer :: status
current_data_line = "1"
call readfile_data(current_data_line, myintvalue1, status)
write(*, *) "status:", status
write(*, *) "Integer : ", myintvalue1
current_data_line = ".true."
call readfile_data(current_data_line, mylogicalvalue1, status )
write(*, *) "status:", status
write(*, *) "Logical : ", mylogicalvalue1
current_data_line = "'Hello from James'"
call readfile_data(current_data_line, myMYDATAvalue1, status )
write(*, *) "status:", status
write(*, *) "MYDATA : ", myMYDATAvalue1
end program test3
C:\gfortran\clf\template_war>c:\gfortran\win64\bin\x86_64-pc-mingw32-gfortran
te
st7.f90 -otest7
C:\gfortran\clf\template_war>test7
status: 0
Integer : 1
status: 0
Logical : T
status: 0
MYDATA : Hello from James
> relaxmike <michael...@gmail.com> wrote:
>> I must say that the method based on implicit statements is
>> very clever, and, based only on fortran statements, allows
>> to minimize the code duplication. But is it possible to
>> declare something like this :
>> module data_mod
>> type MYDATA
>> character(len=20) :: string
>> end type MYDATA
>> implicit type(MYDATA) (Q)
>> include 'test3_template.i90'
>> end module data_mod
>> I don't think so, which shows that the method cannot be extended
>> to abstract data types.
> Yes, it is possible to do something like that. The only problem with the
> above is one of ordering. Implicit statements have to come quite early
> in a scoping unit, before pretty much anything other than USE
> statements. I forget whether it would be ok to just swap the order or
> whether a different ordering constraint prevents that. If that doesn't
> work, defining the derived type in a separate module, which you USE
> here, would work. Derived types are allowed in implicit statements, I'm
> sure.
The problem of ordering should not arise in practice because the
normal thing to do given what is forced upon the user by F90 rules is
to declare the user-defined type in one module along with assignment(=)
and any operators or procedures you want to overload for the type.
Then this module is USEd in the module that defines the template
procedures, which after all need assignment(=) and operators and
procedures overloaded for the type in the body of any template
procedures.
> However, I can't really recommend that approach. The well-known
> error-proneness of implicit typing is greatly magnified in the presence
> of derived types, modules, and host association.
This objection shows that you have never done template programming
in C++. It turns out to be so gawdawful that the normal way to
develop template code is to start with a non-template class and
methods, get that working OK, then convert to templates. Working
in an analogous fashion in Fortran, the non-template version would
have IMPLICIT NONE in force and only when it's working OK would
implicit typing be introduced. Besides IMPLICIT NONE won't help
with host association until the committee gives us some way to
restrict host association.
The really ugly part is: what are you supposed to do when you
need more than 26 template types? Maybe a Chinese Fortran
compiler would become popular at this point. Code with > 26
template types would be harder to read than Chinese, for sure.