# Parametric constructor where type being constructed is parameter

**URL:** https://discourse.julialang.org/t/parametric-constructor-where-type-being-constructed-is-parameter/129866
**Category:** General Usage
**Created:** [June 13, 2025, 5:06pm UTC](https://discourse.julialang.org/t/parametric-constructor-where-type-being-constructed-is-parameter/129866 "2025-06-13T17:06:19Z")
**Posts on this page:** 3
**Page:** 1

<div class="post-metadata">

### Author: ![luke-kiernan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/luke-kiernan/32/214309_2.png) [@luke-kiernan](https://discourse.julialang.org/u/luke-kiernan)
#### Post date: [June 13, 2025, 5:06pm UTC](https://discourse.julialang.org/t/parametric-constructor-where-type-being-constructed-is-parameter/129866/1 "2025-06-13T17:06:19Z")

</div>

I have multiple structs (`FooKey` and `BarKey`), each of which has its own type parameter (`FooType` and `BarType`). Can I write the constructor for these structs in a parametrized manner? Here’s my attempt:

```julia
abstract type AllType end

abstract type FooType <: AllType end
abstract type BarType <: AllType end

abstract type AllKey{T <: AllType} end

struct FooKey{T <: FooType} <: AllKey{T}
    x::Int
end

struct BarKey{T <: BarType} <: AllKey{T}
    x::Int
end

M(::Type{T}, x::Int) where {T <: AllType, M <: AllKey{T}} = M{T}(x)

FooKey(FooType, 1) # should return FooKey{FooType}(1)
BarKey(BarType, 2) # should return BarKey{BarType}(2)

```

But if I do that, Julia treats the function name `M` as a different `M` than the type parameter.

---

<div class="post-metadata">

### Author: ![frankwswang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/frankwswang/32/18561_2.png) [@frankwswang](https://discourse.julialang.org/u/frankwswang)
#### Post date: [June 13, 2025, 7:13pm UTC](https://discourse.julialang.org/t/parametric-constructor-where-type-being-constructed-is-parameter/129866/2 "2025-06-13T19:13:20Z")

</div>

`M` is a type, so it should only be inferred in the type domain. However, Julia disallows directly dispatching methods on a type, but only on type parameters. For example:

```julia
(::M)(args...) where {M} = (M, args) # Illegal

(::Val{M})(args...) where {M} = (M, args) # Legal

```

Based on your code, I think this might be the solution close to your needs:

```julia
julia> struct KeyHolder{K<:AllKey}
           KeyHolder(::Type{K}) where {K<:AllKey} = new{K}()
       end

julia> function (::KeyHolder{K})(::Type{T}, x::Int) where {T<:AllType, K<:AllKey}
           sym = nameof(K)
           constructor = getfield(Main, sym) # Replace `Main` with whatever Module you defined sub-types of `AllKey` in
           constructor{T}(x)
       end

julia> KeyHolder(FooKey)(FooType, 1)
FooKey{FooType}(1)

julia> KeyHolder(BarKey)(BarType, 2)
BarKey{BarType}(2)

```

---

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [June 13, 2025, 8:19pm UTC](https://discourse.julialang.org/t/parametric-constructor-where-type-being-constructed-is-parameter/129866/3 "2025-06-13T20:19:02Z")

</div>

> [@frankwswang](#):
>
> `(::M)(args...) where {M} = (M, args) # Illegal`

It also disallows `::Function` and `::Any` in the callable position, just too general to implement method tables. Restricting `M<:Integer` or some other type won’t work for method tables either, the signature demands a `const` name or an annotation of a type in the callable position.

> [@luke-kiernan](#):
>
> `M(::Type{T}, x::Int) where {T <: AllType, M <: AllKey{T}} = M{T}(x)`  
> But if I do that, Julia treats the function name `M` as a different `M` than the type parameter.

And that’s also why `M` gets treated as a `const` name. There is actually a way to say “a local variable `M` for inputs that subtype the parametric `AllKey`”:

```julia
julia> (M::Type{S} where S<:AllKey)(::Type{T}, x::Int) where {T <: AllType} = M{T}(x)

julia> FooKey(FooType, 1)
FooKey{FooType}(1)

julia> BarKey(BarType, 1)
BarKey{BarType}(1)

```

`Type` is a type, so its annotation is legal in the callable position. Type relations in `where` clauses strictly apply to type parameters in type annotations, _not_ argument names. Same thing applies to positional arguments, and we actually get an informative error:

```julia
julia> bar(M) where M<:Integer = M
ERROR: syntax: function argument and static parameter name not distinct: "M" around REPL[14]:1
Stacktrace:
 [1] top-level scope
   @ REPL[14]:1

julia> bar(X::Type{M} where M<:Integer) = X
bar (generic function with 1 method)

julia> bar(Int), bar(UInt16)
(Int64, UInt16)

```
