Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.
29426 Discussions

overriding abstract generic operator

Patrice_l_
Beginner
2,050 Views

Hi,

I wonder how to write overriding operator when extending an abstract type. One know example writes the interface but not the details of the subroutine.  It seems that the standard force the extended add function to have the same type. and then in  add_my_integer , argument b should be of class my_numeric_type. And thus, we are force to write a select_type to add two integers.  Isn't that just forcing the programmer to define the overriding feature ? Is there a reason to have that design  in the standard ?

 

 

module test
type, abstract :: my_numeric_type
contains
        private
        procedure(op2), deferred :: add
        procedure(op2), deferred :: subtract
         ! procedures for other operations not shown
        generic, public :: operator(+) => add
        generic, public :: operator(-) => subtract
         ! generic specs for other operations not shown
end type my_numeric_type
abstract interface
        function op2(a, b) result(r)
                import :: my_numeric_type
                class(my_numeric_type), intent(in) :: a, b
                class(my_numeric_type), allocatable :: r
        end function op2
end interface
type, extends(my_numeric_type) :: my_integer
                integer, private :: value
        contains
                procedure :: add => add_my_integer
                procedure :: subtract => subtract_my_integer

end type my_integer
contains
function add_my_integer(a,b) result(r)
        class(my_integer),intent(in) :: a
        class(my_numeric_type),intent(in) :: b
        class(my_numeric_type),allocatable :: r
        allocate(my_integer::r)
        r%value=a%value+b%value
end function
function subtract_my_integer(a,b) result(r)
        class(my_integer),intent(in) :: a
        class(my_numeric_type),intent(in) :: b
        class(my_numeric_type),allocatable :: r
        allocate(my_integer::r)
        r%value=a%value-+b%value
end function
end module test

Another thing is the ability to overload procedure like in

type mycomplex
private
contains
procedure :: mycomplex_plus_mycomplex
procedure:: mycomplex_plus_real
procedure, pass(b) :: real_plus_mycomplex
generic:: operator(+) => mycomplex_plus_mycomplex, &
mycomplex_plus_real, real_plus_mycomplex
end type mycomplex

How does the multiple procedure for operator(+) works if operator(+) is defined in abstract, and the standard force everything to be class my_numeric_type. Then the automatic overriding feature based on the type of the parameter can not work ?

 

Thanks.

0 Kudos
5 Replies
Patrice_l_
Beginner
2,050 Views

A full example with what i understand should work is :

module num

  type, abstract :: my_numeric_type
  contains
      private
      procedure(op2), deferred :: add
      generic, public :: operator(+) => add
      procedure(op), deferred :: ass
      generic, public :: assignment(=) => ass
  end type

  abstract interface
      subroutine op(a,b)
          import :: my_numeric_type
          class(my_numeric_type), intent(out) :: a
          class(my_numeric_type), intent(in) :: b
      end subroutine op
      function op2(a,b) result (r)
          import :: my_numeric_type
          class(my_numeric_type), intent(in) :: a,b
          class(my_numeric_type), allocatable :: r
      end function op2

  end interface

  type, extends(my_numeric_type) :: my_integer
      integer, public :: value
  contains
      procedure :: add => add_my_integer
      procedure :: ass => ass_my_integer
      generic, public :: operator(+) => add,add_2_int,add_int_real
  end type

  contains

    function add_2_int(a,b) result(r)
        class(my_integer), intent(in) :: a
        class(my_integer), intent(in) :: b
        class(my_integer), allocatable :: r

                    r%value = a%value+b%value
    end function
function add_int_real(a,b) result(r)
        class(my_integer), intent(in) :: a
        real, intent(in) :: b
        real, allocatable :: r

        r = a%value+b
    end function
function add_my_integer(a,b) result(r)
        class(my_integer), intent(in) :: a
        class(my_numeric_type), intent(in) :: b
        class(my_numeric_type), allocatable :: r

        select type (b)
            type is (my_integer)
                allocate(my_integer :: r)
                select type (r)
                  type is (my_integer)
                    r%value = a%value+b%value
                end select
        end select
    end function
 subroutine ass_my_integer(a,b)
        class(my_integer), intent(out) :: a
        class(my_numeric_type), intent(in) :: b

        select type (b)
            type is (my_integer)
                    a%value = b%value
        end select
    end subroutine

end module

program main
  use num

  class(my_integer), allocatable :: a, b, c
  allocate(my_integer :: a)
  allocate(my_integer :: b)
  allocate(my_integer :: c)
  a=my_integer(1)
  b=my_integer(2)
  c = a+b
  write (*,*) c%value
end program

But then I got those error :

testabstractgeneric2.f90(31): error #8423: In GENERIC type bound procedure definition each binding name must be the name of a specific binding of the type.   [ADD_2_INT]
      generic, public :: operator(+) => add,add_2_int,add_int_real
--------------------------------------------^
testabstractgeneric2.f90(31): error #8423: In GENERIC type bound procedure definition each binding name must be the name of a specific binding of the type.   [ADD_INT_REAL]
      generic, public :: operator(+) => add,add_2_int,add_int_real
------------------------------------------------------^
compilation aborted for testabstractgeneric2.f90 (code 1)

 

0 Kudos
IanH
Honored Contributor III
2,050 Views

The language doesn't permit you to do what you want to do directly - the passed argument of a procedure nominated by a specific binding varies according to the type that overrides the specific binding, but all other arguments must match in terms of type and other characteristics.  So in terms of your first post - yes - you pretty much have to use select type on one of the arguments.

If things weren't this way then generic procedures couldn't be resolved at compile time.  The dynamic type of an object is a run time property.  Consider what happens if you had my_integer and my_real that were both extensions of my_numeric type.  You can do something like:

  CLASS(my_numeric_type), ALLOCATABLE :: a
  CLASS(my_numeric_type), ALLOCATABLE :: b
  CLASS(my_numeric_type), ALLOCATABLE :: c
  ...
  ALLOCATE(my_integer :: a)
  ALLOCATE(my_real :: b)
  ...
  ALLOCATE(c, SOURCE=a+b)

The declared type of a and b is my_numeric_type.  The deferred specific binding that is behind the generic interface for (+) says (+) takes a passed argument that extends my_numeric_type and another argument of my_numeric_type. The expression after the source fits that pattern, so the compiler can resolve the generic binding to a specific binding.  If it was acceptable for the procedure that implements the override of the add specific binding in my_integer to take class(my_integer) for the right hand argument you then have a runtime type mismatch.

In terms of your second post... generic bindings involve a set of specific bindings.  You have you generic bindings referencing stand-alone procedures.  You "want" add_2_int and add_int_real to be behind specific bindings of my_integer.  The problem you are then going to run into though is that you then have ambiguous generics - add_2_int and add_my_integer cannot be distinguished and cannot both sit behind the same generic (given a right hand argument of type my_integer the compiler doesn't know which specific binding the generic resolves to).  Unlike some other languages Fortran is very clear about how generic references map through to specific procedures or bindings in terms of type and kind... it doesn't have a concept that one set of argument types might somehow match better than another set - any sort of ambiguity is an error.

(In terms of rank matching there is a priority of non-elemental specifics over elemental specifics.)

 

0 Kudos
FortranFan
Honored Contributor III
2,050 Views

Patrice l. wrote:

... And thus, we are force to write a select_type to add two integers.  Isn't that just forcing the programmer to define the overriding feature ? Is there a reason to have that design  in the standard ?

..

IanH wrote:

..

So in terms of your first post - yes - you pretty much have to use select type on one of the arguments.

..

Patrice I.,

For my own benefit and learning, are there some concerns or issues with SELECT TYPE construct that you know of and which you can share here? From this post as well as your other post https://software.intel.com/en-us/forums/topic/533445, you seem to suggest some problems with the use of this construct.

0 Kudos
Patrice_l_
Beginner
2,050 Views

FortranFan,

As a fan of fortran, I like performance and  I am trying to write OO fortran in a way that avoid loosing to much with polymorphism,

and stay close to a 'classical' fortran program.  I just don't like unecessary check (if) to do basic operations. In this example, OO might be beautiful to define object that has numeric type and have the ability to change it  (real,int,complex) without having to write different subroutines. But what is the cost ? adding two integer cost  2 select type + one allocate + the actual addition . You see where i am going if you have to do that  10 million times. Unless i am wrong and the compiler is super smart and reduce that to a classical r=a+b . 

This example is only considering non-allocatable intrisic component, when your code start to be complicated, with type including allocatable array of other type with 2 or 3 levels. Ff each time you do an assignment you have to select type plus reallocate , and all of that to do in the end toto%tata%titi%real=toto%tata%titi%real, then you spend a fair amount of time managing object and not really doing computation. Again unless i have a misconception of how the compiler will optimize all those encapsulation layers.

0 Kudos
FortranFan
Honored Contributor III
2,050 Views

Patrice l. wrote:

FortranFan,

As a fan of fortran, I like performance and  I am trying to write OO fortran in a way that avoid loosing to much with polymorphism,

and stay close to a 'classical' fortran program.  I just don't like unecessary check (if) to do basic operations. In this example, OO might be beautiful to define object that has numeric type and have the ability to change it  (real,int,complex) without having to write different subroutines. But what is the cost ? adding two integer cost  2 select type + one allocate + the actual addition . You see where i am going if you have to do that  10 million times. Unless i am wrong and the compiler is super smart and reduce that to a classical r=a+b . 

This example is only considering non-allocatable intrisic component, when your code start to be complicated, with type including allocatable array of other type with 2 or 3 levels. Ff each time you do an assignment you have to select type plus reallocate , and all of that to do in the end toto%tata%titi%real=toto%tata%titi%real, then you spend a fair amount of time managing object and not really doing computation. Again unless i have a misconception of how the compiler will optimize all those encapsulation layers.

Thanks much, I'm now relieved.  As you know, real drivers and considerable thought are needed for OO design and programming.  In our experience, when both these aspects are realized, the benefits of OO far exceed traditional, procedural programming.  For example, without going into details, a quadrature programming library can become extensible, thread-safe/parallelizable, scalable, and thus more computationally efficient overall with OO, not to mention other possible benefits with support and maintenance.  But if the focus is simply on performance of function evaluations, then OO is not worth the effort.

Separately, in Fortran, polymorphic variables and SELECT TYPE do go hand in hand.  Whether to use polymorphic variables is a matter of much, much higher level of thought in terms of code design involving OO principles; once that is decided, I don't think coders need to "penny-pinch" on SELECT TYPE.

Adios,

0 Kudos
Reply