Procedure inheritance rules in Fortran 2003/2008 for class(*)

Procedure inheritance rules in Fortran 2003/2008 for class(*)

The code below compiles in ifort. Accoring to at least one interpretation on the Stackoverflow question it is invalid, but ifort does not produce F2008/F2003 compliance warnings. Is intel's opion that this is correct, that it is an extension, or is it a bug? (if it is deliberate it is useful, but would also be nice to be able to get a warning as it doesn't work in gfortran, and know what the exact rule being applied is).

module classes
Type AData
end Type

Type A
contains
procedure :: Work
end type

Type, extends(AData) :: BData
end Type

Type, extends(A) :: B
contains
procedure :: Work => Work2
end type

contains

subroutine Work(this, D)
class(A) :: this
class(*) :: D
end subroutine

subroutine Work2(this, D)
class(B) :: this
class(BData) :: D
end subroutine

end module classes
24 posts / 0 new
Last post
For more complete information about compiler optimizations, see our Optimization Notice.

Very good question.  To me, this looks like a bug in Intel Fortran compiler relative to Fortran 2003/2008 standard.  Do you have standards checking on when you compile the code (/stand:f08)?

By the way, are you using the latest version, version 14.0, SP1, Update 2 that was released earlier this month?  If not (and if you can upgrade to this version), you should try it out with it since it seems to contain certain enhancements and fixes related to polymorphism.  But do consider getting this reviewed by Intel Premier Support also.

Here's the wording from the Intel Fortran standard:

Per line 13 of the above-mentioned section 4.5.7.3 in the standard, the dummy argument D in your Work procedure must have the same characteristics.  But in my reading, they are clearly two different types since CLASS(*) can be anything (INTEGER, REAL, CHARACTER, CLASS(AData), etc.) which can be distinguished from CLASS(BData) in a SELECT TYPE construct.

Attachments: 

We don't tend to introduce deliberate extensions of this nature. It may just be a bug. We'll take a look - thanks.

Steve Intel Developer Support

Thanks. There's also the related case

subroutine Work2(this, D)
class(B) :: this
class(*), target :: D
class(BData), pointer :: B

B=>D    
end subroutine

which also compiles in ifort but not in gfortran. (see updated stackoverflow question; I personally think this should be correct but with optional warnings)

Does the BData type have the BIND or SEQUENCE attribute?  If not, the pointer assignment is not legal code, and the compiler should diagnose it.  If the BData type does have the BIND or SEQUENCE attribute, then the compiler should complain about your use of it in a polymorphic declaration type specifier.  Either way - this should not compile.

(A diagnostic isn't required for the argument mismatch case, but it would be polite for the compiler to issue one, given I think it always (?) has all the information it needs to see there's a mismatch to hand.)

The type system is there to protect you.  Don't try and fight it.

Quote:

antony wrote:

...

    ...
    class(*), target :: D
    class(BData), pointer :: B
    ...
    B=>D

...

I'd think the above code structure, of an unlimited polymorphic target in a pointer assignment, would be invalid under any circumstances.  So I'm surprised the compiler doesn't raise that as an error.  One for Intel compiler folks to think about.

Quote:

IanH wrote:

..

A diagnostic isn't required for the argument mismatch case, but it would be polite for the compiler to issue one, given I think it always (?) has all the information it needs to see there's a mismatch to hand.

...

Isn't the issue one of consistency?  If the TBP in the parent type had any other declaration other than unlimited polymorphic type, say integer :: D or class(adata) :: D, then I'd think the compiler would issue a diagnostic about the mismatch with the overriding procedure in the child (unless some compilation flags can be invoked to suppress it).  So shouldn't it do the same with class(*) :: D declaration (assuming same compilation flags are used)?

The case in the original post has been escalated as issue DPD200253724. I agree that ifort should not accept this case.

I am looking at the second issue now.

Steve Intel Developer Support

The case with pointer assignment is now issue DPD200253728. Ian correctly identifies that this code violates constraint C715 in F2008, which means that the compiler is required to have the ability to diagnose the violation.

I do note that we DO detect the case where D has TARGET in Work2 but not in Work.

Steve Intel Developer Support

Thanks for checking, that's a pity! Beware that if you roll this out in a patch it will break some published code - e.g. my CosmoMC program (responsible for probably dozens of ifort sales over the last year..) has used class(*) for convenient casting of procedure arguments from base classes for a while now, and there are also third-party published likelihood class extensions from recent data which will also be incompatible if this is disallowed.

I'd just assumed it was a useful feature...  to make it pedantic standard compliant I'll have to introduce several intermediate up-casting procedures with up to three levels of nested select types. I guess this is a general issue for factory-model oop implementations in any language with strong type casting, where many functions operate on several specific types that are manufactured. The trouble is fortran doesn't have any equivalent of generics to make it easier.

Given that gfortran already complains about this usage, I'm not terribly worried about published code breaking.

What you want might be TYPE(*) from the "Enhanced C Interoperability" Technical Spec TS29113, to be included in Fortran 2015. But this isn't implemented yet.

Steve Intel Developer Support

Quote:

antony wrote:

Thanks for checking, that's a pity! Beware that if you roll this out in a patch it will break some published code - e.g. my CosmoMC program (responsible for probably dozens of ifort sales over the last year..) has used class(*) for convenient casting of procedure arguments from base classes for a while now, and there are also third-party published likelihood class extensions from recent data which will also be incompatible if this is disallowed.

I'd just assumed it was a useful feature...  to make it pedantic standard compliant I'll have to introduce several intermediate up-casting procedures with up to three levels of nested select types. I guess this is a general issue for factory-model oop implementations in any language with strong type casting, where many functions operate on several specific types that are manufactured. The trouble is fortran doesn't have any equivalent of generics to make it easier.

Bugs happen unfortunately, but one fixes them and moves on.  But how can Intel ever be constrained based on code in circulation that might make use of bugs?

Thus far, you've not provided a convincing rationale for why you need to do what you're trying to do:

  1. The second issue re: your use of unlimited polymorphic target is clearly fraught with danger and no one can make a case that Intel compiler should continue allowing it.
  2. As to your first issue, on your posting at StackOverflow.com, a reader (francescalus) provided a nice workaround using Fortran 2003 GENERIC capability.  Did you try it?  What is wrong with that approach?

"a general issue for factory-model oop implementations in any language with strong type casting, where many functions operate on several specific types that are manufactured. The trouble is fortran doesn't have any equivalent of generics to make it easier." - I don't agree: this is too much of a leap in argument, difficult to buy since you're not making use of available generics options or stating why you can't use them.  I find one can go far with most OOP design patterns (GoF) with existing Fortran 2003/2008 capabilities, even if the Fortran implementations require some extra coding compared to, say, C++ or Java.  A separate thing to consider: many of these design patterns are not always the best approaches or the most efficient way to design code.  The value of these patterns is more in getting one to think differently, often in an educational setting, as opposed to providing a set blueprint for real code.

 

It should certainly be fixed to give an error or at least warning, but likewise it is sensible to warn people if changes will break existing code. (gfortran is unfortunately not quite yet usable in many instances)

Using fortran generics does not help if you call the procedures from a scope with only base class types (the test call from my comment in stackoverflow does not work). [by "generics" in my post I meant generic programming not fortran generic procedudes: class<T1, T2> and similar in other languages, which I think helps with this sort of issue in Java, C#, Delphi, etc.]

Another reason for somtimes having class(*) arguments in the base class here is the nasty problems with circular module dependencies. That at least should be mostly resolved if any compilers ever implement fortran standard submodules. Interesting possibility from type(*) if it's actually allowed in inherited procedures in this way.

We have always felt it appropriate to add diagnostics for incorrect code. We can't always predict what sort of existing code will encounter new diagnostics, but we're glad to help customers understand the issue. In most cases they are happy to have the compiler reveal errors in their code, even if it means having to rewrite some things. Just because something compiled without errors in older versions, that doesn't mean that the code actually did something correct.

Steve Intel Developer Support

Incidentally, there are other non-class(*) argument checks that are also missed, e.g. it accepts this oddity

module classes

implicit none
    
    Type AData
    end Type

    Type, extends(AData) :: BData
    end Type

    Type A
    contains
    procedure :: Work
    end type

    Type, extends(A) :: B
    contains
    procedure :: Work => Work2
    end type

    contains

    subroutine Work(this, D)
    class(A) :: this
    class(BData)  :: D

    end subroutine

    subroutine Work2(this, D)
    class(B) :: this
    class(AData) :: D

    end subroutine

    end module classes

 

Quote:

antony wrote:

Incidentally, there are other non-class(*) argument checks that are also missed, e.g. it accepts this oddity

module classes

implicit none
    
    Type AData
    end Type

    Type, extends(AData) :: BData
    end Type

    Type A
    contains
    procedure :: Work
    end type

    Type, extends(A) :: B
    contains
    procedure :: Work => Work2
    end type

    contains

    subroutine Work(this, D)
    class(A) :: this
    class(BData)  :: D

    end subroutine

    subroutine Work2(this, D)
    class(B) :: this
    class(AData) :: D

    end subroutine

    end module classes

 

I don't see anything wrong with the above - I think the compiler is correct in this case.

I think this is a classic case of polymorphism.

Since Adata is the parent of Bdata, D in Work2 can be considered to have the same characteristics as D in Work.  The other way around will be a problem i.e., if D in Work were to be declared as AData while declaring D as BData in Work2.

I agree it's functionally fine, but if the rule is arguments must match exactly (excelpt class), it's not right (gfortran rejects it).

Also ifort does not check the "intent"-ness of the dummy arguments, where gfortran does.

Quote:

antony wrote:

Also ifort does not check the "intent"-ness of the dummy arguments, where gfortran does.

If you can provide an example (like above), that can lead to an escalation with a tracking incident number like the other two cases.

I am pretty sure we do track INTENT, but I will check this (and the other issue.)

Steve Intel Developer Support

The issue with INTENT is interesting. We do detect Work not specifying INTENT and Work2 specifying INTENT(IN). But if Work2 says INTENT(INOUT), we let that through and shouldn't - INOUT is not the same as not specifying INTENT. I have attached this and the different classes case to DPD200253724 as they're all part of the same problem.

Steve Intel Developer Support

Also the reverse of the first case is rejected by gfortran, eg

 

    module classes
    implicit none

    Type AData
    end Type

    Type, extends(AData) :: BData
    end Type

    Type A
    contains
    procedure :: Work
    end type


    Type, extends(A) :: B
    contains
    procedure :: Work => Work2
    end type

    contains

    subroutine Work(this, D)
    class(A) :: this
    Type(AData), intent(in)  :: D

    end subroutine

    subroutine Work2(this, D)
    class(B) :: this
    Type(AData) :: D

    end subroutine

    end module classes

 

Quote:

antony wrote:

The code below compiles in ifort.

 ... would also be nice to be able to get a warning as it doesn't work in gfortran, ..


module classes
Type AData
end Type

Type A
contains
procedure :: Work
end type

Type, extends(AData) :: BData
end Type

Type, extends(A) :: B
contains
procedure :: Work => Work2
end type

contains

subroutine Work(this, D)
class(A) :: this
class(*) :: D
end subroutine

subroutine Work2(this, D)
class(B) :: this
class(BData) :: D
end subroutine

end module classes

I am curious about recent developments with gfortran, so finally got around to downloading and setting it up for Windows.  The latest, convenient version I could find was MinGW 4.8.1 build.

NOTE: the above code builds without any errors/warnings in gfortran, MinGW 4.8.1 build:

mingw32-gfortran.exe -JDebug\GNU\ -Wall -g -std=f2008 -Wrealloc-lhs-all -Wrealloc-lhs
-Wimplicit-interface -Wconversion -Warray-temporaries -Wall
-c C:\dev\Fortran\TypeExtension\sor\ProcOverride.f90
-o Debug\GNU\sor\ProcOverride.o
Process terminated with status 0 (0 minute(s), 0 second(s))
0 error(s), 0 warning(s) (0 minute(s), 0 second(s))

So perhaps a newer gfortran version (4.9+) has been improved on this procedure overriding aspect, but I can't find the newer version for Windows.  4.8.1 version also doesn't complain if the dummy argument in the overriding procedure uses an extended type.   However 4.8.1 version does catch mismatches in the dummy argument INTENT.

My (obvious) take-away:

  1. Fortran 2008 (and some 2003) implementations are still "in the works" in most compilers,
  2. Coders need to think through and use new features with appropriate caution,
  3. Cross-checking using different compilers can help, especially with the newer features,
  4. Test, test, test.. report issues so everyone can benefit

Thanks to Antony for this bringing this up,

We have fixed the issues discussed in this thread - I expect the fixes to appear in the 15.0 release later this year.

Steve Intel Developer Support

Login to leave a comment.