Fortran2003: указатель процедуры на функцию, возвращающую указатель на полиморфный тип

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

В качестве конкретного примера рассмотрим следующий интерфейс функции:

abstract interface
   function if_new_test(lbls) result(t)
      import :: test_t
      class(test_t),pointer       :: t
      character(len=*),intent(in) :: lbls(:)
   end function if_new_test
end interface

И код использования должен иметь указатель на процедуру, который может указывать на функции с этим интерфейсом:

procedure(if_new_test),pointer :: nt

Я спрашиваю, законно ли это, потому что gfortran (4.7.2) жалуется на объявление указателя процедуры с сообщением:

Ошибка: переменная CLASS 'nt' в (1) должна быть фиктивной, доступной для размещения или указателем.

Я не понимаю этого сообщения об ошибке, поскольку nt сам по себе является указателем, а функция, на которую он указывает, также является указателем.

Для справки следует полный исходный код примера. Во-первых, модуль, содержащий мои производные типы, интерфейсы и функции / подпрограммы:

module test_m

   implicit none

   type :: test_t
      character(len=10) :: label
      contains
      procedure :: print => print_test
   end type test_t

   type,extends(test_t) :: test2_t
      character(len=10) :: label2
      contains
      procedure :: print => print_test2
   end type test2_t

   abstract interface
      function if_new_test(lbls) result(t)
         import :: test_t
         class(test_t),pointer       :: t
         character(len=*),intent(in) :: lbls(:)
      end function if_new_test
      subroutine if_make_test(t,lbls)
         import :: test_t
         class(test_t),pointer       :: t
         character(len=*),intent(in) :: lbls(:)
      end subroutine if_make_test
   end interface

   contains

   subroutine print_test(self)
      implicit none
      class(test_t),intent(in) :: self
      print *, self%label
   end subroutine print_test

   subroutine print_test2(self)
      implicit none
      class(test2_t),intent(in) :: self
      print *, self%label, self%label2
   end subroutine print_test2

   function new_test(lbls) result(t)
      implicit none
      class(test_t),pointer       :: t
      character(len=*),intent(in) :: lbls(:)
      call make_test(t,lbls)
   end function new_test

   function new_test2(lbls) result(t)
      implicit none
      class(test_t),pointer       :: t
      character(len=*),intent(in) :: lbls(:)
      call make_test2(t,lbls)
   end function new_test2

   subroutine make_test(t,lbls)
      implicit none
      class(test_t),pointer       :: t
      character(len=*),intent(in) :: lbls(:)
      allocate(test_t::t)
      t%label = lbls(1)
   end subroutine make_test

   subroutine make_test2(t,lbls)
      implicit none
      class(test_t),pointer       :: t
      character(len=*),intent(in) :: lbls(:)
      allocate(test2_t::t)
      select type(t) ! so the compiler knows the actual type
         type is(test2_t)
            t%label  = lbls(1)
            t%label2 = lbls(2)
         class default
            stop 1
      end select
   end subroutine make_test2  

end module test_m

И основная программа, использующая этот модуль:

program test

   use test_m
   implicit none

   class(test_t),pointer           :: p
   procedure(if_make_test),pointer :: mt
   procedure(if_new_test),pointer  :: nt

   mt => make_test
   call mt(p,["foo"])
   call p%print
   deallocate(p)

   mt => make_test2
   call mt(p,["bar","baz"])
   call p%print
   deallocate(p)

   p => new_test(["foo"])
   call p%print
   deallocate(p)

   p => new_test2(["bar","baz"])
   call p%print
   deallocate(p)

   nt => new_test
   p => nt(["foo"])
   call p%print
   deallocate(p)

   nt => new_test2
   p => nt(["bar","baz"])
   call p%print
   deallocate(p)

end program test

Программа сначала создает объекты с помощью подпрограмм make_test и make_test2, и в моем тестировании это работает со всеми компиляторами, которые я пробовал. Затем объекты создаются путем прямого вызова функций new_test и new_test2, что также работает в моих тестах. Наконец, объекты должны снова создаваться с помощью этих функций, но косвенно через указатель процедуры nt.

Как указано выше, gfortran (4.7.2) не компилирует объявление nt.

ifort (12.0.4.191) выдает внутреннюю ошибку компилятора в строке nt => new_test.

pgfortran (12.9) компилируется без предупреждения, и исполняемый файл дает ожидаемые результаты.

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


person Frank    schedule 15.02.2013    source источник
comment
Вероятно, вам следует зарегистрировать это как ошибку против gfortran, сообщение об ошибке определенно неверное.   -  person sigma    schedule 16.02.2013
comment
Предупреждение - функции, возвращающие указатель, опасны с точки зрения незначительных изменений синтаксиса, приводящих к утечкам памяти - подумайте, что произойдет, если кто-то использует функцию с правой стороны оператора присваивания, а не присваивание указателя. F2008 (потенциально) привнес некоторые дополнительные сложности, связанные с их использованием в качестве фактического аргумента. Избегайте, если у вас нет веских причин. Распределение здесь лучше, особенно после того, как поддержка полиморфного присвоения из F2008 стала широко распространенной.   -  person IanH    schedule 16.02.2013


Ответы (1)


Кажется, ваш код в порядке. Я мог без проблем скомпилировать его как с Intel 13.0.1, так и с NAG 5.3.1. У старых компиляторов могут быть проблемы с более «причудливыми» функциями Fortran 2003.

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

module test_m
  implicit none

  type :: test_t
    character(len=10) :: label
  contains
    procedure :: print => print_test
  end type test_t

  type,extends(test_t) :: test2_t
    character(len=10) :: label2
  contains
    procedure :: print => print_test2
  end type test2_t

  abstract interface
    function if_new_test(lbls) result(t)
      import :: test_t
      class(test_t), allocatable :: t
      character(len=*),intent(in) :: lbls(:)
    end function if_new_test

    subroutine if_make_test(t,lbls)
      import :: test_t
      class(test_t), allocatable :: t
      character(len=*),intent(in) :: lbls(:)
    end subroutine if_make_test
  end interface

contains

  subroutine print_test(self)
    class(test_t), intent(in) :: self
    print *, self%label
  end subroutine print_test

  subroutine print_test2(self)
    class(test2_t), intent(in) :: self
    print *, self%label, self%label2
  end subroutine print_test2

  subroutine make_test(t,lbls)
    class(test_t), allocatable :: t
    character(len=*),intent(in) :: lbls(:)
    allocate(test_t::t)
    t%label = lbls(1)
  end subroutine make_test

  subroutine make_test2(t,lbls)
    class(test_t), allocatable :: t
    character(len=*),intent(in) :: lbls(:)
    allocate(test2_t::t)
    select type(t) ! so the compiler knows the actual type
    type is(test2_t)
      t%label  = lbls(1)
      t%label2 = lbls(2)
    class default
      stop 1
    end select
  end subroutine make_test2

end module test_m


program test
   use test_m
   implicit none

   class(test_t), allocatable :: p
   procedure(if_make_test), pointer :: mt

   mt => make_test
   call mt(p, ["foo"])
   call p%print
   deallocate(p)

   mt => make_test2
   call mt(p, ["bar","baz"])
   call p%print
   deallocate(p)

end program test

Еще одно замечание: неявный оператор none на уровне модуля «наследуется» процедурами модуля, поэтому вам не нужно использовать его в каждой дополнительной подпрограмме.

person Bálint Aradi    schedule 15.02.2013
comment
Спасибо за подтверждение. Я протестировал его с последним снимком состояния gcc, и действительно, теперь он компилируется без предупреждений и дает ожидаемые результаты. - person Frank; 15.02.2013
comment
вы не сможете вернуть полиморфный тип в результате функции - что вы имеете в виду? Вы имеете в виду невозможность иметь функцию с распределяемым полиморфным результатом в качестве выражения правой части в операторе присваивания? Если это так, ALLOCATE(lhs, SOURCE=rhs(..)) - это простой способ обхода проблемы в F2003. - person IanH; 16.02.2013
comment
Да, действительно, я имел в виду это. Знаете ли вы, работает ли это без обходного пути в Fortran 2008? Я действительно не понимаю, почему такое назначение не должно быть принципиально возможным с размещаемыми объектами, если оно работает с указателями. - person Bálint Aradi; 16.02.2013
comment
Стандарт 2008 года больше не запрещает внутреннее присваивание полиморфным размещаемым объектам, поэтому со временем это должно поддерживаться. В конце концов, многие компиляторы уже поддерживают подобное неявное (пере) выделение размещаемых массивов. - person sigma; 17.02.2013