Указатель процедуры Fortran на подпрограммы в производном типе

В Фортране мне нужен указатель процедуры внутри производного типа, который может указывать на одну из нескольких подпрограмм. Эта проблема, по-видимому, обычна для SO:

Сохранение процедуры Fortran как свойства в производном типе

Перегрузка процедур с привязкой к типу в Fortran 2003

Для этого не существует соответствующей подпрограммы вызов универсальной подпрограммы с привязкой к типу

Общие процедуры с привязкой к типу и аргументами процедуры

Процедура с привязкой к типу в качестве аргументов

назвать несколько. Ответ на этот вопрос для функций очень хорошо представлен в первом справочнике.

Однако мне до сих пор неясна методология разработки такого кода в случае, если указатель процедуры с привязкой к типу указывает на подпрограмму. Сложность, похоже, заключается в том, что с тем, что возвращается, не связано никакого типа (поскольку на самом деле ничего не «возвращается»).

Я также хотел бы указать на нюанс, что, хотя простое решение может существовать в более позднем стандарте fortran (2003,2008), это решение может работать не на всех компиляторах, что может быть проблематичным в будущем. Так что меня интересуют решения, удобные для компиляторов.

У меня есть небольшой код (показанный ниже), который в настоящее время работает, но в моем большом коде я получаю внутреннюю ошибку компилятора (также показанную ниже) в файле, где я использую указатели процедур в производных типах. Мой вопрос: что я могу сделать с приведенным ниже кодом, чтобы

1) Строго используйте явные интерфейсы

2) Максимизируйте информацию, передаваемую компилятору

3) Убедитесь, что код переносится между как можно большим количеством компиляторов (т. Е. Используйте стандарты fortran 90/95).

В какой степени может быть выполнено вышеперечисленное (1 является наиболее важным)? Возможно ли удовлетворить всем этим критериям, указанным выше? Я знаю, что «удовлетворить всем этим критериям» - это субъективно, но я бы сказал, что ответ «да» на тот же вопрос, касающийся функций, а не подпрограмм.

 gcc version 5.1.0 (i686-posix-dwarf-rev0, Built by MinGW-W64 project)

Небольшой код:

  module subs_mod
  implicit none
  public :: add,mult
  contains
  subroutine add(x,y,z)
    implicit none
    integer,intent(inout) :: x
    integer,intent(in) :: y,z
    x = y+z
  end subroutine
  subroutine mult(x,y,z)
    implicit none
    integer,intent(inout) :: x
    integer,intent(in) :: y,z
    x = y*z
  end subroutine
  end module

  module type_A_mod
  use subs_mod
  implicit none
  public :: type_A,init,operate
  type type_A
    procedure(),pointer,nopass :: op
  end type
  contains
  subroutine init(A,op)
    implicit none
    external :: op
    type(type_A),intent(inout) :: A
    A%op => op
  end subroutine
  subroutine operate(A,x,y,z)
    implicit none
    type(type_A),intent(in) :: A
    integer,intent(inout) :: x
    integer,intent(in) :: y,z
    call A%op(x,y,z)
  end subroutine
  end module

  program test
  use type_A_mod
  use subs_mod
  implicit none
  type(type_A) :: A
  integer :: x
  call init(A,mult)
  call operate(A,x,3,5)
  write(*,*) 'x = ',x
  end program

Ошибка компилятора в большом коде:

    f951.exe: internal compiler error: Segmentation fault
    libbacktrace could not find executable to open
    Please submit a full bug report,
    with preprocessed source if appropriate.
    See <http://sourceforge.net/projects/mingw-w64> for instructions.

ОБНОВЛЕНИЕ

Вот небольшая модификация, которая дает компилятору больше информации, но я не пробовал это на большом коде. Однако это кажется произвольным, и я понятия не имею, поможет это или нет.

  ...
  function add(x,y,z) result(TF)
  ...
    logical :: TF
    x = y+z
    TF = .true.
  end function
  function mult(x,y,z) result(TF)
  ...
    logical :: TF
    x = y*z
    TF = .true.
  end function
  end module

  module type_A_mod
  ...
  type type_A
    procedure(logical),pointer,nopass :: op
  end type
  ...
  subroutine init(A,op)
    implicit none
    logical,external :: op
  ...
  end subroutine
  subroutine operate(A,x,y,z)
  ...
    logical :: TF
    TF = A%op(x,y,z)
  end subroutine
  end module

  program test
  ...
  end program

КОММЕНТАРИИ К РЕШЕНИЮ Просто чтобы прокомментировать решение (предоставленное @IanH): была одна дополнительная проблема, а именно то, что у меня были некоторые производные типы, входящие в абстрактный интерфейс, что, согласно Новые возможности Fortran 2003, выражение Import должно быть включено, чтобы сделать абстракцию интерфейс знает о любых вводимых производных типах. Вот небольшой рабочий пример, который, примененный к большому коду, смягчает внутреннюю ошибку компилятора, которую я имел :)

  module DT_mod
  implicit none
  private
  public :: DT
  type DT
    integer :: i
  end type
  contains
  end module

  module subs_mod
  use DT_mod
  implicit none
  private
  public :: add,mult,op_int

  abstract interface
  subroutine op_int(d,x,y,z)
    import :: DT
    implicit none
    type(DT),intent(inout) :: d
    integer,intent(inout) :: x
    integer,intent(in) :: y,z
  end subroutine
  end interface

  contains
  subroutine add(d,x,y,z)
    implicit none
    type(DT),intent(inout) :: d
    integer,intent(inout) :: x
    integer,intent(in) :: y,z
    x = y+z
    d%i = 1
  end subroutine
  subroutine mult(d,x,y,z)
    implicit none
    type(DT),intent(inout) :: d
    integer,intent(inout) :: x
    integer,intent(in) :: y,z
    x = y*z
    d%i = 2
  end subroutine
  end module

  module type_A_mod
  use DT_mod
  use subs_mod
  implicit none
  private
  public :: type_A,init,operate
  type type_A
    procedure(op_int),pointer,nopass :: op
  end type
  contains
  subroutine init(A,op)
    implicit none
    procedure(op_int) :: op
    type(type_A),intent(inout) :: A
    A%op => op
  end subroutine
  subroutine operate(A,d,x,y,z)
    implicit none
    type(DT),intent(inout) :: d
    type(type_A),intent(in) :: A
    integer,intent(inout) :: x
    integer,intent(in) :: y,z
    call A%op(d,x,y,z)
  end subroutine
  end module

  program test
  use type_A_mod
  use subs_mod
  use DT_mod
  implicit none
  type(type_A) :: A
  type(DT) :: d
  integer :: x,y,z
  y = 3; z = 5
  call init(A,mult)
  call operate(A,d,x,y,z)
  write(*,*) 'x,y,x = ',y,z,x
  write(*,*) 'd%i = ',d%i
  end program

Любая помощь приветствуется.


person Charles    schedule 12.04.2016    source источник
comment
Подумайте о том, чтобы просто показать разницу, трудно увидеть, что вы изменили в обновлении 2, особенно учитывая, что вы не используете пустые строки в своем коде (что затрудняет чтение).   -  person Vladimir F    schedule 13.04.2016
comment
Кстати, попробуйте -std=f95 в gfortran, и вы увидите, сколько вещей на самом деле Fortran 2003 и новее.   -  person Vladimir F    schedule 13.04.2016
comment
Спасибо @VladimirF, недавно попробовал, это помогло.   -  person Charles    schedule 13.04.2016


Ответы (2)


Указатели процедур не были частью стандартного языка до Fortran 2003, поэтому, если вы хотите их вообще использовать, совместимость с Fortran 95 не имеет значения.

Внутренняя ошибка компилятора - это ошибка компилятора, независимо от источника, предоставленного компилятору.

Не существует такой вещи, как указатель на процедуру, привязанный к типу. У вас либо есть процедура с привязкой к типу, которая объявляется после CONTAINS в конструкции производного типа, либо указатель на процедуру, который может быть компонентом типа или автономным объектом. Указатель процедуры, который является компонентом, является частью значения объекта производного типа - он может быть связан с различными процедурами во время выполнения. Процедура, связанная с типом, является фиксированным свойством объявления типа.

Если вы хотите, чтобы указатель процедуры (или фиктивная процедура) имел явный интерфейс, вы должны указать имя интерфейса внутри скобок оператора объявления процедуры.

procedure(interface_name_goes_here) [, pointer, ...] :: thing_being_declared

Предоставляемое имя интерфейса может быть именем доступной конкретной процедуры (включая одну, ранее объявленную другим оператором объявления процедуры) или именем абстрактного интерфейса.

(Если имя интерфейса в операторе объявления процедуры является типом, как и для компонента в вашем примере кода, объявленная процедура является функцией с результатом данного типа с неявным интерфейсом.

Если имя интерфейса в операторе объявления процедуры полностью отсутствует, объявляемая процедура может быть функцией или подпрограммой (ее последующее использование в этом случае должно согласовываться с тем или другим) с неявным интерфейсом.)

Итак, предполагая, что вы хотите объявить компонент указателя процедуры с явным интерфейсом к функции (вопреки заголовку вопроса) с теми же характеристиками, что и add или mult во втором фрагменте кода:

TYPE type_A
  PROCEDURE(the_interface), POINTER, NOPASS :: op
END TYPE type_A

ABSTRACT INTERFACE
  FUNCTION the_interface(x, y, z) RESULT(tf)
    IMPLICIT NONE
    ! function modifying arguments - poor style!!!
    INTEGER, INTENT(INOUT) :: x
    INTEGER, INTENT(IN) :: y, z
    LOGICAL :: tf
  END FUNCTION the_interface
END INTERFACE

Если вы хотите, чтобы указатель процедуры был подпрограммой с явным интерфейсом (что предпочтительнее функции, изменяющей свои аргументы), измените абстрактный интерфейс соответствующим образом.

Фиктивная процедура в подпрограмме init не обязательно должна быть указателем - внутри init вы не меняете то, на что ссылается op вещь - вы просто указываете на нее другим указателем:

PROCEDURE(the_interface) :: op

Когда ваши фиктивные процедуры и указатели процедур объявляются с явным интерфейсом, я ожидаю, что разумный компилятор диагностирует любые несоответствия в характеристиках.

person IanH    schedule 13.04.2016
comment
Это выглядит очень многообещающе. У меня осталась одна проблема с этой реализацией. Когда я пытаюсь запустить процедуру в моем большом коде (вызовите init (A, mult) в маленьком коде), я получаю сообщение об ошибке, в котором говорится о несоответствии интерфейса в фиктивной процедуре «оператор» в (1): НАМЕРЕННОЕ несоответствие в аргументе 'Икс'. Тем не менее, теперь я смог скомпилировать файл, который ранее приводил к внутренней ошибке компилятора. - person Charles; 13.04.2016

Вот мой рабочий пример:

module obj_mod
    integer, parameter :: n = 5

    type obj_type
        procedure(sub_interface), pointer, nopass :: obj_sub => NULL()
    end type

    interface
        subroutine sub_interface(y, x)
            import n
            double precision, dimension(n) :: x, y
        end subroutine sub_interface
    end interface

contains 
    subroutine sq_sub(x, y)
        double precision, dimension(n) :: x, y
        y = x ** 2
    end subroutine

    subroutine exp_sub(x, y)
        double precision, dimension(n) :: x, y
        y = exp(x)
    end subroutine

end module 

program member_subroutine
    use obj_mod
    type(obj_type) obj
    double precision, dimension(n) :: x, y

    x = (/ 1, 2, 3, 4, 5 /)
    write(*,*) 'x =', x

    obj%obj_sub => sq_sub
    call obj%obj_sub(x, y)
    write(*,*) 'y1 =', y

    obj%obj_sub => exp_sub
    call obj%obj_sub(x, y)
    write(*,*) 'y2 =', y
end program member_subroutine
person Oliver Evans    schedule 09.04.2018
comment
Всегда лучше описать решение, а не просто сбросить код. Что сделал ОП не так? Или чем ваше решение отличается? А как насчет пунктов 1, 2 и 3 в вопросе? - person Vladimir F; 09.04.2018