How to express one type related to another

I am trying to express something like this:

struct C{T}
    foo::SomeTypeThatDependsOnT
end

In C++ one could just do T::FavoriteType but I’m not sure what the analogue is in julia. I can write a function that returns a type, dependent on the argument, but I don’t think that’s going to help the compiler.

Here’s one approach that failed:

struct A
end

struct Related{T}
end

# attempt to specialize for specific types
# next is illegal: Redefinition of a constant Related
struct Related{A}
    a::Int
end

Perhaps:

julia> struct C{T, V <: Vector{T}}
           foo::V
       end

julia> C(rand(1))
C{Float64, Vector{Float64}}([0.06108395582464021])

julia> C(Number[1])
C{Number, Vector{Number}}(Number[1])

That’s a neat trick. Unfortunately, the relation between the type and the related type is more idiosyncratic in my case.

You’ll ultimately need to use something like the the suggestion above, with “redundant” type parameters. You can use constructors (especially inner constructors) to perform the proper calculations and ensure valid combinations.

See recent discussion on Simple compile-time evaluation for struct types.

Here’s an example

struct C{T, S}
    foo::S

    function C(foo::T) where T
        S = promote_type(Float32, T) # calculation to determine S
        S <: Real || error("computed unusable type parameter S=$S") # validate S
        return new{T, S}(convert(S, foo))
    end
end
5 Likes

Could you provide the C++ code that does what you want? I’m not familiar with C++ so I need the extra help, I can’t even immediately look up parametric structs in C++. I can’t figure out what you’re going for from the failed Julia example, it looks backwards because usually the T is inferred from the call C(foo_value), so it’s more like struct C{SomeTypeThatDependsOnT, T} foo::T end. I can tell you now though that both variable types need to be put in the parameters list.

Since you don’t know C++ and I’m a little rusty on it, I’m not sure it will help, but here goes:

// C++, not julia
class A {
public:
    typedef SomeWeirdClass MyResult;  //MyResult becomes and alias for previous type
};

class B  {
public:
    // defines a type MyResult equal to the structure
    // doesn't create an instance of MyResult;
    struct MyResult {
       float result;
       float error;
    };
};

With this in place, A::MyResult is type SomeWeirdClass while B::MyResult is the type given in the struct definition.

Although this suggests that the related type is the return value of a class method, that will not necessarily be the case in my anticipated use.

Yeah that sounds completely different from the first post. I don’t claim to really understand what’s going on, but that looks like classes A and B form namespaces, and the typedef is doing a type alias. Types don’t make namespaces in Julia, and we can’t nest type definitions in each other, either. If you don’t need A and B to be types, it looks like this:

abstract type SomeWeirdClass end # assume in Main global scope

module A
  import ..Main: SomeWeirdClass  # ..Main refers to Main.Main
  const MyResult = SomeWeirdClass # constant assignment serves as type alias
end

module B
  struct MyResult
    result::Float32
    error::Float32
  end
end

# aliases don't copy, they refer to the original and print as such
A.MyResult, B.MyResult # (SomeWeirdClass, Main.B.MyResult)

Correct on both points.

As for modules, my reading of the docs is that modules can’t be used as parameters for a template, at least not unless using Val{MyModule}, so I’m not sure if using modules would help much. I can do

module MyA
    struct Related
        f::Float64
    end
end

module MyB
    struct Related
        i::Int32
    end
end

foo(x::MyB.Related) = println("Related structure in B has i = ", x.i)
foo(x::MyA.Related) = println("Related in A has f = ", x.f)

function go(m)
    x = m.Related(3)  # type unstable
    foo(x)
end

go(MyB)   # Related structure in B has i = 3

So I can pass around the module as a variable and use it to access the type. But I doubt the compiler is going to be able to work out the types, and so I suspect performance would not be great.

Getting back to my first post, what I had in mind was that one could do with structures what one can with functions. In julia one can define f(x::A) so that it is completely different from f(x::B). So my thought was that the struct for struct Related{A} could be completely different from the definition for struct Related{B}. It’s not a perfect parallel since functions use the class of an instance while my fictional struct syntax uses the class directly.

In julia today template parameters are just placeholder names, not concrete types, and, apparently, a given structure name like Related can only be defined once. So there’s no specialization of structures.

The idea was partly inspired by template specialization in C++, which I recall does allow one to generate different code for specific classes.

I don’t know what you mean, the ::MyB.Related annotations? I think that’s fine, module names are constants (but the kind that can be reassigned to cause obsolete inlined references, so be careful not to do that).

Separate parametric type definitions with the same name are impossible in the same namespace. You need separate modules and distinguish the types with ModA.Related vs ModB.Related.

When I asked for an example, I was thinking of this but I really couldn’t find anything that did something similar to what you seemed to be saying, and you didn’t end up providing a C++ MWE.

Don’t call these templates, that is not a thing in Julia. My intuition is that C++ templates really do just work differently from Julia’s parametric types. I assume that Related templated by A would contain A::MyResult, and the type A would internally define a type MyResult. In Julia, a method can associate two types getResult(::Type{A}) = MyResult, but like you said, a parameter T in a struct Related{T} definition is a placeholder, and crucially, getResult(T) evaluates during definition (and fails) instead of during instantiation where A can be provided. You can handle this in a constructor method because that runs during instantiation, but in the definition itself, you still need 2 parameters in the header Related{T, DependsOnT}.
The reason for this limitation is Julia’s interactivity. In C++, code is set in stone by the time it reaches the compiler. In Julia, getResult can be redefined whenever, so it’d be disastrously ambiguous if you could instantiate and compile methods for a MyResult-based Related{A}, redefine getResult(::Type{A}) = YourResult, then instantiate and compile methods for a YourResult-based Related{A}. Only way to remove that ambiguity is to have another parameter Related{A, MyResult} vs Related{A, YourResult}.