The relevant section of the manual is this one. As @lmiq pointed out, parametric constructors are kinda special, they are the only kinda of function (or function-like?) that can be passed explicit types (without them being a positional argument).
As @lmiq also pointed out, you can put type restrictions directly in the struct
definition (this is covered by the link to the manual I provided, and the manual explains how this is equivalent to automatically defining a constructor with a where
clause).
Carefully reading this section I noticed one thing. No example in the whole section involves defining an outer constructor (i.e., a constructor outside the struct
scope) that had the {T, ...}
clause between the function name and the opening parenthesis. The only examples of constructor definitions with this syntax are of inner constructors.
However, as was pointed out in this thread, there is code that defines outer constructors that receive explicit type parameters and work anyway. I agree with @CameronBieganek we can only guess is that there is some kind of ambiguity problem that arises in some situations but not in others.
I would bet on the following: on the minimal example provided by @lmiq the constructor has no way to guess what is the type T
. Foo{T}(::UndefInitializer)
calls Foo(Vector{T}(undef,2))
, what Foo
(with no type parameters) will guess to be T
? It received a Vector{T}
, the struct
has only a field x :: S
but it is parametrized as Foo{T,S <: Vector{<:T}}
, the Vector{T}
allow us to assert that S == Vector{T}
, but we cannot guess what T
(the struct
parameter) is, because if it is a Vector{Int}
that is being passed (i.e., S == Vector{Int}
), we have that Int <: T
and, therefore, T
can be many things, like Integer
or Number
. The problem does not happen if T
is fixed (instead of being any supertype) because then it is clear that T
can only be Int
.