My gut tells me that this constructor doesn’t exist because it’s only straightforward in this case, where T
is only used once. Consider:
struct E{T <: Real}
a::T
b::Vector{T}
end
Now think about calling E(1, [1.0])
- which argument (if any) should be converted? Both E(1.0, [1.0])
and E(1, [1])
are valid and you also immediately run into problems when you have e.g. E(Int8(-1), [0xff])
because both conversions and a type promotion throw:
julia> convert(Int8, 0xff)
ERROR: InexactError: check_top_bit(Int8, 255)
julia> convert(UInt8, Int8(-1))
ERROR: InexactError: check_top_bit(UInt8, -1)
julia> promote(Int8(-1), 0xff)
ERROR: InexactError: check_top_bit(UInt8, -1)
The non-parametric version with explicit Int
and Vector{Int}
fields is perfectly fine with any conversion, because such an ambiguity can never occur! All fields can be promoted/converted in isolation. So IMO it’s perfectly ok to say “no, be specific with what you want to enforce” when talking about parametric types.
There is a default constructor - that is exactly what C
(no parameters) is. That is just a distinct method & method table from C{T}
:
What’s really going on here is that Point
, Point{Float64}
and Point{Int64}
are all different constructor functions. In fact, Point{T}
is a distinct constructor function for each type T
.
[…]
When the type is implied by the arguments to the constructor call, as in Point(1,2)
, then the types of the arguments must agree – otherwise the T
cannot be determined – but any pair of real arguments with matching type may be given to the generic Point
constructor.
and to prove that:
julia> struct C{T}
n::T
v::Vector{Real}
end
julia> methods(C)
# 1 method for type constructor:
[1] C(n::T, v::Vector{Real}) where T in Main at REPL[1]:2
julia> methods(C{Int})
# 1 method for type constructor:
[1] C{T}(n, v) where T in Main at REPL[1]:2
julia> methods(C)[1] === methods(C{Int})[1]
false
### with differently typed fields:
julia> struct C{T <: Real}
n::T
v::Vector{T}
end
julia> methods(C)
# 1 method for type constructor:
[1] C(n::T, v::Vector{T}) where T<:Real in Main at REPL[1]:2
julia> methods(C{Int})
# 1 method for type constructor:
[1] C{T}(n, v) where T<:Real in Main at REPL[1]:2
julia> methods(C)[1] === methods(C{Int})[1]
false
So no, the behavior is not a bug or broken, it is an expected consequence of an ambiguity that comes up when this (inevitably) has to be generalized to more than one argument constraining the T
.