Community
cancel
Showing results for 
Search instead for 
Did you mean: 
Highlighted
New Contributor I
54 Views

type to class conversion in call with optional arguments

Out of curiosity, does anybody know whether the following code is standard conform or not. This was due to a minor oversight, but took a while to figure out, as the ifort compiler does not give any meaningful error messages but crashs with a segfault, whereas gfortran just works. The problem is that in sub1, argument p is declared with type(t) and in sub2 it is declared with class(t). program optional type :: t integer, pointer :: x end type t type(t) :: a integer, pointer :: y allocate(a%x, source=1) call sub1(a, c=y) contains subroutine sub1(a, p, c) class(t), intent(in) :: a type(t), optional, intent(out) :: p integer, pointer, optional, intent(out) :: c call sub2(a, p, c) end subroutine sub1 subroutine sub2(a, p, c) class(t), target, intent(in) :: a class(t), optional, intent(out) :: p integer, pointer, optional, intent(out) :: c if (present(p)) then p%x => a%x else c => a%x end if end subroutine sub2 end program optional
0 Kudos
28 Replies
Highlighted
Valued Contributor I
53 Views

The restrictions for a dummy argument which is not present are in 18-007 (the F2018 standard document) on 15.5.2.12p3 where I don't see any restriction. Your code compiles with nagfor, with gfortran and with PGI fortran. 

 

0 Kudos
Highlighted
Valued Contributor III
53 Views

My hunch too is your code conforms to the current standard (Fortran 2008).

I think it's a bug in Intel Fortran with a basic aspect introduced in the language starting with Fortran 90 involving OPTIONAL dummy arguments but manifesting in the compiler with the feature of polymorphic dummy arguments introduced with Fortran 2003.

You may want to submit a support request at the Intel OSC: https://supporttickets.intel.com/?lang=en-US

In the meantime, you may want to look into working around the segmentation fault by doing something "silly" but functional like so:

   subroutine sub1(a, p, c)
      class(t), intent(in)                    :: a
      type(t), optional, intent(out)          :: p
      integer, pointer, optional, intent(out) :: c
      if ( present(p) .and. present(c) ) then
         call sub2(a, p, c)
      else if ( present(p) ) then
         call sub2(a, p=p)
      else if ( present(c) ) then
         call sub2(a, c=c)
      else
         call sub2(a)
      end if
   end subroutine sub1

 

0 Kudos
Highlighted
Valued Contributor III
53 Views

Or a simpler variant of a workaround focusing on the polymorphic dummy argument in sub2 may also suffice:

   subroutine sub1(a, p, c)
      class(t), intent(in)                    :: a
      type(t), optional, intent(out)          :: p
      integer, pointer, optional, intent(out) :: c
      if ( present(p) ) then
         call sub2(a, p, c)
      else
         call sub2(a, c=c)
      end if
   end subroutine sub1

 

0 Kudos
Highlighted
Valued Contributor I
53 Views

FortranFan wrote:

Or a simpler variant of a workaround focusing on the polymorphic dummy argument in sub2 may also suffice:

   subroutine sub1(a, p, c)
      class(t), intent(in)                    :: a
      type(t), optional, intent(out)          :: p
      integer, pointer, optional, intent(out) :: c
      if ( present(p) ) then
         call sub2(a, p, c)
      else
         call sub2(a, c=c)
      end if
   end subroutine sub1

 

Yes, this one is obvious, but I think that was the main point of the discussion that the segmentation fault is only present of the optional intent(out) is class(t) instead of type(t). 

0 Kudos
Highlighted
New Contributor I
53 Views

Thanks for the answers. Looks like I should report this as a bug. My obvious work-around to locate the problem was the simpler variant 2. However, in the real code, where I stumbled across this, the almost only purpose of sub2 is to hide the use of present() and obtain simple interfaces and compact code. So the work-around defeats this purpose. Also using generic interfaces instead of optional arguments is not possible, as in the real case the type of x encapsulated in t is class(*) instead of integer. And with class(*) generic interfaces often become impossible. Anyway, the obvious solution is to either use type or class in both cases. Being rather new to oop-fortran I find it sometimes difficult to choose between the use of type or class in such cases as these intent(out) return values. class seems to be the obvious choice in case I want to extend derived-type t later on. But are there any obvious downsides (except for the possibly slightly worse performance)?
0 Kudos
Highlighted
Valued Contributor III
53 Views

Martin wrote:

.. in the real case the type of x encapsulated in t is class(*) instead of integer. And with class(*) generic interfaces often become impossible.

.. Being rather new to oop-fortran I find it sometimes difficult to choose between the use of type or class in such cases as these intent(out) return values ..

Object-oriented analysis and design *before* doing any object-oriented programming seems to help in such matters.

Don't know if this is relevant to OP's real code, but it may prove easier to work with code if as a code design consideration, one can avoid unlimited poiymorphic objects, or at least such components in derived types, and the use of allocatable attribute is preferred over pointer.

0 Kudos
Highlighted
New Contributor I
53 Views

FortranFan wrote:

Object-oriented analysis and design *before* doing any object-oriented programming seems to help in such matters.

Don't know if this is relevant to OP's real code, but it may prove easier to work with code if as a code design consideration, one can avoid unlimited poiymorphic objects, or at least such components in derived types, and the use of allocatable attribute is preferred over pointer.

At this point, it is just playing around, trying to find a good way to implement general container classes such as sets, maps etc. class(*) seems to be the only way to achieve this without any quirky stuff. This example is derived from a lookup type routine. The pointer is used to avoid potentially large copies if complex objects are stored. Otherwise allocatable makes life much easier. In particular move_alloc allows fast transfer of objects into and from containers. All in all current fortran does not seem to be very suitable for such general purpose classes, but it works ok. Regarding type versus class: I think this has not much to do with oop design in general, but is something fortran specifc. Using type signals that routines and derived types are not really intended to be extended in the future, and might be a little more performant. Or are there any other motivations for using type over class?
0 Kudos
Highlighted
Black Belt Retired Employee
53 Views

I'll just throw in the general observation that anytime you defer decisions to run-time, you have a performance penalty. In the case of passing an item declared TYPE to one declared CLASS, there can be a lot of overhead setting up the "class descriptor" information for the call.

--
Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
New Contributor I
53 Views

If I have some spare time, I will check produced assembler to get a feeling for the overhead of going from type to class. I was already (and still are) wondering about this overhead and how the compiler is handling a routine like:

sub(...)
type(t) :: x

call x%sub1(...)
call x%sub2(...)
call x%sub3(...)
end sub

As sub[123] are type bound procedures, argument x passed to these subroutines is declared class(t). So is the class header setup thrice or is the compiler able to optimise it to just one setup, ideally at creating time of x.

I am also a bit worried (performance wise) that transforming old-style ADT without type-bound procedures into a proper class will degrade performance in critical code places. As the self/this argument needs to be declared as class, this overhead cannot be avoided. Once I thought that using type(t) in declaration allows the compiler to resolve calls at compile-time and to bypass v-table access and potential pointer-chasing. But if type t is in its own module and IPO is not used, this is not going to happen I guess.

Anyway, time-critical code where oop features are to be avoided tend to account only for a minor amount of code even in numerical applications, whereas organisational stuff (which can easily run at half the speed without much penalty overall) usually takes up most of the code. Moreover, having generic (but slow) containers using class(*) features at hand makes life easier even for time critical pieces of code during prototyping/experimenting phase. So I am quite happy at the state of fortran.

0 Kudos
Highlighted
New Contributor I
53 Views

Martin wrote:

As sub[123] are type bound procedures, argument x passed to these subroutines is declared class(t). So is the class header setup thrice or is the compiler able to optimise it to just one setup, ideally at creating time of x.

I am also a bit worried (performance wise) that transforming old-style ADT without type-bound procedures into a proper class will degrade performance in critical code places. As the self/this argument needs to be declared as class, this overhead cannot be avoided. Once I thought that using type(t) in declaration allows the compiler to resolve calls at compile-time and to bypass v-table access and potential pointer-chasing. But if type t is in its own module and IPO is not used, this is not going to happen I guess.

Yes, in your example above the compiler should definitely be able to avoid a vtable call (no matter if the type t is implemented in a separate module or not). But: The compiler still needs to insert a bit of code for converting TYPE to CLASS (for all three calls, unless the compiler is so clever to optimize it into a single conversion).

If, on the other hand, x is declared as class(t), then you need a vtable lookup, but no conversion. Not sure which variant is more performant in the end.

Cheers,

Janus

0 Kudos
Highlighted
Valued Contributor III
53 Views

Martin wrote:

.. Regarding type versus class: I think this has not much to do with oop design in general, ..

I think otherwise, the decision to declare a dummy argument as TYPE or CLASS as in the optional argument p in the sub1 procedure of the original post is about the use of polymorphism with that function parameter.  Object-oriented analysis (OOA) and design (OOD), if done diligently, can help bring considerable discipline and provide guidance toward employing the OO features of polymorphism and inheritance *only* as needed in conjunction with the goals and requirements of one's code, and not willy-nilly as many who are starting off with OO or get enamored by it are wont to do.

0 Kudos
Highlighted
New Contributor I
53 Views

FortranFan wrote:

I think otherwise, the decision to declare a dummy argument as TYPE or CLASS as in the optional argument p in the sub1 procedure of the original post is about the use of polymorphism with that function parameter.  Object-oriented analysis (OOA) and design (OOD), if done diligently, can help bring considerable discipline and provide guidance toward employing the OO features of polymorphism and inheritance *only* as needed in conjunction with the goals and requirements of one's code, and not willy-nilly as many who are starting off with OO or get enamored by it are wont to do.

This sounds a bit like the BDUF/waterfall design models. Otherwise careful analysis and avoidance of over-engineering (use components over inheritance) are self-evident. Nevertheless, I still would like to hear some fortran specific advantages of type over class (mainly as used in argument declarations). For example in my case where I have a type t, and a subroutine returning (intent(out)) an object of this type, no matter how carefully I plan and analyse, I might still find out in a couple of months or years, that I wish to extend t and still use existing routines. I can always call sub(x%t) for a variable x of this extended type. But this reads awkwardly. Advantages of using type are performance and improved compile-time type checking and error-analysis. But otherwise? Do you have a concrete-example where type offers (some other kind of) advantage over class?

0 Kudos
Highlighted
Valued Contributor III
53 Views

Martin wrote:

.. Nevertheless, I still would like to hear some fortran specific advantages of type over class (mainly as used in argument declarations). ..

In my findings, given my use of the OO features with mostly non-numerical aspects of coding (though these increasingly appear as important or more compared to hardcore number-crunching) and the relative lack of advancement in Fortran compiler optimizations of code involving derived type components, I'm yet to see any advantages or disadvantages with the use of polymorphic objects beyond compiler-time checking and code readability, maintenance, etc.

In terms of OO capabilities in Fortran involving polymorphism and inheritance (e.g., with type extension), where the language-specific considerations come into play is with another aspect altogether.  That is with difficulties in employing the first principle of OO which is encapsulation: Fortran modules only allow PRIVATE and PUBLIC attributes for module entities and a much required *third column* (e.g., internal in Microsoft .NET languages or protected in C++) is missing in Fortran.  This really curtails, in my opinion, the development of good code, one is forced to make a lot of things PUBLIC which then defeats encapsulation.  But of course this is a separate issue from TYPE/CLASS considerations involving polymorphic objects or not as dummy arguments. 

0 Kudos
Highlighted
New Contributor I
53 Views

Yeah, the current state of private/public is a catastrophe. Almost not usable once a "one type per module" policy is followed, which I mostly do. I have already been considering using a trailing underscore (much like the leading underscore in python, but leading underscores are not allowed) to mark private/protected methods. I like this aspect of python. Compile-time checking of following the rules would not be that crucial in my opinion.

0 Kudos
Highlighted
Beginner
53 Views

Just my 5 cents from fortran oop in hard core number crunching

  • oop is king. It allows to the code to reflect the mathematical logic of special cases and their generalization. With that it offers an enormous amount of flexibility
  • 1 type 1 module is a catastrophe. I started with that but the limitations on inheritance and access to "sibs" is a nightmare. Now I am back to putting as many types within an inheritance path into on single module and handle their routines via submodules. However, the function headers must sit in the module ......... so easily +1000 lines per module file.
  • for hand-core number crunching the general rule remains: keep the data together. Regarding that oop helps to organize the code but only as a container provider around (huge) arrays of intrinsic types. With that inheritance allows to apply different functions to the same array content again reflecting general and special cases. Unlimited polymorphic pointers to scalars (not arrays) of intrinsic types are a recipe for writing java speed code in fortran if applied on a large scale.
  • After almost a decade of fortran programming and having increasingly enjoyed oop, if anyone ask me today which close-to-the-metal language to learn nowadays ....................... c++ (I am just to deep in the mud to change)

Cheers

0 Kudos
Highlighted
Black Belt
53 Views

Martin wrote:

Almost not usable once a "one type per module" policy is followed, which I mostly do.

That strikes me a crazy policy.  What motivates it? 

Modules are meant to be packages of related things. If you have procedures supporting two types that need to see into the internals of both types, then that indicates those types are pretty closely related.

0 Kudos
Highlighted
New Contributor I
53 Views

ianh wrote:

Quote:

Martin wrote:

 

Almost not usable once a "one type per module" policy is followed, which I mostly do.

 

 

That strikes me a crazy policy.  What motivates it?

As a non-native speaker I might have chosen the wrong wording. I just use one type per module where it makes sense. If types are very closely related or really small, then I have more than one in a module. I use subdirectories to organise packages.

One advantage is that routine names can be kept short. Imagine I have type t which extends type s, and t overloads a routine sub. Keeping both in one module means that I have to choose different names for the two variants. Also I find that smaller files are somewhat easier to navigate. To me, putting all together into a large module is just a tiny bit like spaghetti code on a module level.

0 Kudos
Highlighted
Valued Contributor III
53 Views

may.ka wrote:

  • .. access to "sibs" is a nightmare..
  • .. if anyone ask me today which close-to-the-metal language to learn nowadays ....................... c++ (I am just to deep in the mud to change),,

@may.ka,

Can you provide a use case for "sibs" in the kind of compute-heavy coding you do?

What is your definition of a "close-to-the-metal language"?

0 Kudos
Highlighted
Beginner
53 Views

@FortranFan

The code snippet below implements a matrix class with two sibs, a class of squared symmetric matrices
and a class of factors of squared symmetric matrices. Both are direct offspring of the parent class.
This is because both have different arguments for their initialization thus making the factor a child
of the squared_symmetric doesn't work (without inventing procedure name extensions).

In the current implementation every type sits in its own module. This would not compile because the
sib class "matrix_squared_symmetric_factor" is not know to the class "matrix_squared_symmetric".
As the object of class "matrix_squared_symmetric_factor" is indeed the factor of an object of
class "matrix_symmetric_squared" it can be critical for inversion routines to know whether the
factor has already been calculated (the factor is 2/3 of the whole inversion operation).
This knowledge might be communicated via the pointer association status

In my applications val(:,:) might be of 150,000 x 150,000 or more. Therefore depending on the task it might be
necessary to either keep the factor (and maybe in go into swap) or constantly move memory between the
matrix and its factor.

Module Mod_Matrix_parent
  Type, abstract :: Matrix_parent
    Real(kind=8), allocatable :: val(:,:)
  End type Matrix_parent
End Module Mod_Matrix_parent
Module Mod_Matrix_Squared_Symmetric
  Type, extends(Matrix_parent) :: Matrix_Squared_Symmetric
    Type(Matrix_Squared_Symmetric_Factor), Pointer :: tsfac=>null()
  contains
    Procedure, Pass :: Init => SubInit
    Procedure, Pass :: Invert => SubInvert
    Procedure, Pass :: Invert1 => SubInvert1
    Procedure, Pass :: Invert2 => SubInvert2
  End type Matrix_Squared_Symmetric
contains
  Subroutine SubInit(this,RMIn)
    Class(Matrix_Squared_Symmetric), Intent(inout) :: this
    Real(kind=8), Intent(In) :: RMIn
  end Subroutine SubInit
  Subroutine SubInvert(this)
    if(associated(this%tsfac)) Then
      call this%invert1()
    else
      call this%invert2()
    End if
  end Subroutine SubInvert
End Module Mod_Matrix_Full
Module Mod_Matrix_Squared_Symmetric_Factor
  Type, extends(Matrix_parent) :: Matrix_Squared_Symmetric_Factor
  contains
    Procedure, Pass :: Init => SubInit
  End type Matrix_Squared_Symmetric_Factor
contains
  Subroutine SubInit(this,tsin)
    Class(Matrix_Squared_Symmetric_Factor), Intent(inout) :: this
    Type(Matrix_Squared_Symmetric), Intent(In) :: tsin
  end Subroutine SubInit
End Module Mod_Matrix_Factor

 

Close-to-the-metal means that one has(is allowed) to do stuff by hand (eg. memory allocation etc) and with that having some control over what the code will do at run time. As an example, the automatic allocation in f08 is handy, but who knows what the compiler does if an array is copied into another  array which has the attribute "allocatable" but has been allocated some zillion lines way. Maybe the compiler will just newly allocate it. In some applications this may induce a slow down which renders the program unusable. In some application one may lose pointer association status. Interpreted languages like R and pyhton (I am only loosely familiar with pyhton) are very handy for tiny quick and dirty tasks but number crunching is still the domain of compiled languages like fortran and c/c++ (maybe julia in the future). When functions become time critical R and python usually resort to routines written in fortran or c/c++. However, I am a scientist on a computer, not a computer scientist ........... so I am happy about any knowledge transfer.

cheers

0 Kudos