Inferring Parametric Types that aren't the last one

I apologize in advance for the basic question. I’m trying to write an outer constructor for the following MWE that infers the numeric type T from the arguments. The final constructor fails, because you’re only allowed to exclude trailing type parameters in Julia. What is the right way to add an outer constructor for this type that infers T from the arguments?

struct Foo{T<:Real,N,M}
    a::T
    b::NTuple{N,T}
    Foo{T,N,M}(a::T, b::NTuple{N,T}) where {T<:Real,N,M} = new{T,N,M}(a, b)
end

const Bar{T} = Foo{T,2,3}

# convert input types to T
Foo{T,N,M}(a, b::Tuple{Vararg{<:Number,N}}) where {T<:Real,N,M} = Foo{T,N,M}(T(a), T.(b))
# constant b
Foo{T,N,M}(a, b::Number) where {T<:Real,N,M} = Foo{T,N,M}(a, ntuple(i -> b, N))
# infer T (doesn't work - how to fix)
Foo{N,M}(a, b) where {N,M} = Foo{promote_type(typeof(a), eltype(b)),N,M}(a, b)

I’m also interested in inferring N if b is a Tuple.

Edit: I’ll clarify that I’m looking to be able to use both Foo{2,3}(1.2, 3.4) and Bar(1.2, 3.4). Even if I swap the parameter order (which break other parts of my code) to {N,M,T}, Bar(1.2, 3.4) does not find a defined method.

I don’t think that is possible. I.e. Foo{N, M} simply means Foo{N, M, S} where S.

julia> Foo{M,N} where {M,N} == Foo{M,N,S} where {M,N,S}
true

If you put the T last in the definition of Foo you can omit it.

Also, a minor point: Your first outer constructor should probably be written

Foo{T,N,M}(a, b::NTuple{N,Number}) where {T<:Real,N,M} = Foo{T,N,M}(T(a), T.(b))

The construction you use with a Tuple{Vararg{<:Number, N}} is deprecated in 1.12.

julia> (1, 2.0) isa Tuple{Vararg{<:Number, N}} where N
WARNING: Wrapping `Vararg` directly in UnionAll is deprecated (wrap the tuple instead).
You may need to write `f(x::Vararg{T})` rather than `f(x::Vararg{<:T})` or `f(x::Vararg{T}) where T` instead of `f(x::Vararg{T} where T)`.
To make this warning an error, and hence obtain a stack trace, use `julia --depwarn=error`.
true

I think it will be better to use some ordinary functions for instantiation of Foo objects. The outer constructor methods are quite restricted.

Thank you for the feedback and advice. It took me a little while to figure out why it didn’t need to be NTuple{N,<:Number}) - Tuple typing seems to behave differently than everything else.

Btw, this seems like the best solution I could come up with - reordering the types and adding the final outer constructor as a functor so that something like Bar(1, 2.0) worked.

struct Foo{N,M,T<:Real}
    a::T
    b::NTuple{N,T}
    Foo{N,M,T}(a::T, b::NTuple{N,T}) where {N,M,T<:Real} = new{N,M,T}(a, b)
end

const Bar{T} = Foo{2,3,T}

# convert input types to T
Foo{N,M,T}(a, b::NTuple{N,Number}) where {N,M,T} = Foo{N,M,T}(T(a), T.(b))
# constant b
Foo{N,M,T}(a, b::Number) where {N,M,T} = Foo{N,M,T}(a, ntuple(i -> b, N))
# infer T (how?)
(::Type{Foo{N,M,<:Any}})(a, b) where {N,M} = Foo{N,M,promote_type(typeof(a), eltype(b))}(a, b)

What exactly do you mean by using ordinary functions for instatiating Foo objects? Do you mean defining a function foo() and associated methods?