# Ambiguity with Vararg outer constructor and inner constructor

**URL:** https://discourse.julialang.org/t/ambiguity-with-vararg-outer-constructor-and-inner-constructor/54622
**Category:** General Usage
**Tags:** question, constructors
**Created:** [February 4, 2021, 2:38pm UTC](https://discourse.julialang.org/t/ambiguity-with-vararg-outer-constructor-and-inner-constructor/54622 "2021-02-04T14:38:33Z")
**Posts on this page:** 9
**Page:** 1

<div class="post-metadata">

### Author: ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)
#### Post date: [February 4, 2021, 2:38pm UTC](https://discourse.julialang.org/t/ambiguity-with-vararg-outer-constructor-and-inner-constructor/54622/1 "2021-02-04T14:38:33Z")

</div>

I have a working `Point` type that works smoothly in 2D and 3D with various outer constructors:

```julia
using StaticArrays

struct Point{Dim,T}
  coords::SVector{Dim,T}
end

# convenience constructors
Point{Dim,T}(coords::NTuple{Dim,V}) where {Dim,T,V} = Point{Dim,T}(SVector(coords))
Point{Dim,T}(coords::Vararg{V,Dim}) where {Dim,T,V} = Point{Dim,T}(SVector(coords))
Point(coords::NTuple{Dim,T}) where {Dim,T} = Point{Dim,T}(SVector(coords))
Point(coords::Vararg{T,Dim}) where {Dim,T} = Point{Dim,T}(SVector(coords))
Point(coords::AbstractVector{T}) where {T} =
  Point{length(coords),T}(SVector{length(coords)}(coords))

# coordinate type conversions
Base.convert(::Type{Point{Dim,T}}, coords) where {Dim,T} = Point{Dim,T}(coords)
Base.convert(::Type{Point{Dim,T}}, p::Point) where {Dim,T} = Point{Dim,T}(p.coords)
Base.convert(::Type{Point}, coords) = Point{length(coords),eltype(coords)}(coords)

# type aliases for convenience
const Point2 = Point{2,Float64}
const Point3 = Point{3,Float64}
const Point2f = Point{2,Float32}
const Point3f = Point{3,Float32}

```

I can for example create the following points:

```julia
# most commonly used
Point((1,2)) # figures out that (Dim, T) = (2, Int)
Point(1,2) # the same without the extra parenthesis
Point([1, 2]) # from a vector because most users expect it to work

# forcing different types
Point{2,Float32}((1.0,2.0)) # converts from Float64 to Float32
Point{2,Float32}(1.0, 2.0) # the same without parenthesis

# with cleaner syntax
Point2f((1.0,2.0)) # same as previous section
Point2f(1.0, 2.0) # same as previous section

# converting from array syntax
Point2f[(1,2), (3,4)] # vector of points with Float32 coordinates

```

The problem starts when I try to do the same with 1D points, i.e. a single coordinate. The code enters in an infinite loop because the outer `Vararg` constructor keeps calling itself as opposed to calling the inner constructor with `SVector`:

```julia
Point(1.0) # infinite loop here
Point((1.0,)) # the same infinite loop

```

I expected a `Point{1,Float64}` as the result. Is this intended behavior? How can I preserve the functionality above and still handle the 1D case? I appreciate any help, I’ve been struggling with this dispatch rule for a while now, and other people are trying to help too (cc: @FPGro).

---

<div class="post-metadata">

### Author: ![FPGro](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fpgro/32/20822_2.png) [@FPGro](https://discourse.julialang.org/u/FPGro)
#### Post date: [February 4, 2021, 3:29pm UTC](https://discourse.julialang.org/t/ambiguity-with-vararg-outer-constructor-and-inner-constructor/54622/2 "2021-02-04T15:29:43Z")

</div>

To clarify: the problem is this line:

> [@juliohm](#):
>
> `Point{Dim,T}(coords::Vararg{V,Dim}) where {Dim,T,V} = Point{Dim,T}(SVector(coords))`

For the one dimensional case `Point{Dim,T}(::Vararg{V,Dim})` is ambiguous with the (implicit) default constructor, which should approximately be `Point{Dim,T}(::Any)`. As it stands now, the outer constructor appears to be more specific and is recursively called for one dimensional input. My proposed change was `Point{Dim,T}(coord::V, coords::Vararg{V}) where {Dim,T,V}`, which is now apparently less specific than the default constructor and is now never called for 1D input. **Can somebody point me at a reference for how julia determines specificity?** It’s a tiny bit confusing in this case.  
Anyways, my proposed fix mostly works, but leads to the inconsistency that `Point{2,Type}(x, y)` works fine (Varargs constructor), but `Point{1,Type}(x)` now calls the inner constructor in all cases, so it fails for the usual input types like Ints and Floats.

My next idea would be providing an explicit inner constructor with restricted type `::AbstractArray` or even `::StaticArray`, which should hopefully catch the outputs of all outer constructors but still let the Varargs constructor handle cases like `Point{1,Int}(1)`.

Any thoughts on that?

If everything fails, I can still do a little manual dispatch to the inner constructor with a dummy type, but that would be less elegant.

---

<div class="post-metadata">

### Author: ![CameronBieganek](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cameronbieganek/32/6915_2.png) [@CameronBieganek](https://discourse.julialang.org/u/CameronBieganek)
#### Post date: [February 4, 2021, 7:07pm UTC](https://discourse.julialang.org/t/ambiguity-with-vararg-outer-constructor-and-inner-constructor/54622/3 "2021-02-04T19:07:13Z")

</div>

This seems to work:

```julia
using StaticArrays

struct Point{Dim,T}
  coords::SVector{Dim,T}
end

# convenience constructors
Point{Dim,T}(coords::NTuple{Dim,V}) where {Dim,T,V} = Point{Dim,T}(SVector(coords))
Point{Dim,T}(coords...) where {Dim,T} = Point{Dim,T}(coords)
Point(coords::NTuple{Dim,T}) where {Dim,T} = Point{Dim,T}(SVector(coords))
Point(coords...) = Point(coords)
Point(coords::AbstractVector{T}) where {T} =
  Point{length(coords),T}(SVector{length(coords)}(coords))

# coordinate type conversions
Base.convert(::Type{Point{Dim,T}}, coords) where {Dim,T} = Point{Dim,T}(coords)
Base.convert(::Type{Point{Dim,T}}, p::Point) where {Dim,T} = Point{Dim,T}(p.coords)
Base.convert(::Type{Point}, coords) = Point{length(coords),eltype(coords)}(coords)

# type aliases for convenience
const Point2 = Point{2,Float64}
const Point3 = Point{3,Float64}
const Point2f = Point{2,Float32}
const Point3f = Point{3,Float32}

```

---

<div class="post-metadata">

### Author: ![FPGro](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fpgro/32/20822_2.png) [@FPGro](https://discourse.julialang.org/u/FPGro)
#### Post date: [February 4, 2021, 7:36pm UTC](https://discourse.julialang.org/t/ambiguity-with-vararg-outer-constructor-and-inner-constructor/54622/4 "2021-02-04T19:36:43Z")

</div>

Almost, I like reducing the Vararg constructors to calling tuple constructors. However,

````julia
julia> Point{1,Int}((1))
ERROR: MethodError: Cannot `convert` an object of type Int64 to an object of type SVector{1, Int64}
Closest candidates are:
  convert(::Type{SVector{N, T}}, ::CartesianIndex{N}) where {N, T} at C:\Users\gross\.julia\packages\StaticArrays\NTbHj\src\SVector.jl:46
  convert(::Type{SA}, ::Tuple) where SA<:StaticArray at C:\Users\gross\.julia\packages\StaticArrays\NTbHj\src\convert.jl:13
  convert(::Type{SA}, ::SA) where SA<:StaticArray at C:\Users\gross\.julia\packages\StaticArrays\NTbHj\src\convert.jl:12
  ...
and the same happens for `Point{1,Int}(1)`, which was causing problems before.
```
````

---

<div class="post-metadata">

### Author: ![CameronBieganek](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cameronbieganek/32/6915_2.png) [@CameronBieganek](https://discourse.julialang.org/u/CameronBieganek)
#### Post date: [February 4, 2021, 7:45pm UTC](https://discourse.julialang.org/t/ambiguity-with-vararg-outer-constructor-and-inner-constructor/54622/5 "2021-02-04T19:45:46Z")

</div>

Actually, it does work for the one element tuple, just not for the one scalar call:

```julia
julia> Point{1,Int}((1,))
Point{1,Int64}([1])

julia> Point{1,Int}(1)
ERROR: MethodError: Cannot `convert` an object of type Int64 to an object of type SArray{Tuple{1},Int64,1,1}

```

🤔

---

<div class="post-metadata">

### Author: ![FPGro](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fpgro/32/20822_2.png) [@FPGro](https://discourse.julialang.org/u/FPGro)
#### Post date: [February 4, 2021, 7:50pm UTC](https://discourse.julialang.org/t/ambiguity-with-vararg-outer-constructor-and-inner-constructor/54622/6 "2021-02-04T19:50:29Z")

</div>

Oh yeah, that `,` caught me off guard. But it’s just a tiny step from there. I just tried to include my second proposal by changing the definition of point to

```julia
struct Point{Dim,T}
  coords::SVector{Dim,T}
  Point{Dim,T}(coords::AbstractVector) where {Dim,T} = new{Dim,T}(coords)
end

```

and that seems to work now as intended. Thanks.  
Any further comments?

Edit: StaticArrays defines a convert from AbstractArray to StaticArray [here](https://github.com/JuliaArrays/StaticArrays.jl/blob/59f92e0ca7ac391a850a6e7a2ce1eb53aa237fc4/src/convert.jl#L28), so this can be considered safe in the inner constructor.

---

<div class="post-metadata">

### Author: ![FPGro](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fpgro/32/20822_2.png) [@FPGro](https://discourse.julialang.org/u/FPGro)
#### Post date: [February 4, 2021, 8:19pm UTC](https://discourse.julialang.org/t/ambiguity-with-vararg-outer-constructor-and-inner-constructor/54622/7 "2021-02-04T20:19:02Z")

</div>

Another recursion I just discovered, those are really funny:

```julia
julia> Point{2,Int}((3,3,3))
ERROR: InterruptException:
Stacktrace:
 [1] Point{2, Int64}(coords::Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Tuple{Int64, Int64, Int64}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}})
   @ Main ~\Desktop\meshes.jl:10
 [2] top-level scope
   @ REPL[10]:1

```

That’s a lot of Tuples. `Point{Dim,T}(coords::NTuple{Dim,V}) where {Dim,T,V}` does only catch cases where dimensions match of course.  
Since StaticArrays contains a guard against that, they seem to have encountered the same problem: I suggest to also introduce this:

```julia
Point{Dim,T}(coords::Tuple{Tuple{Tuple{<:Tuple}}}) where {Dim,T} = 
    throw(DimensionMismatch("No precise constructor for Point{$(Dim),$(T)} found. Length of input was $(length(coords[1][1][1]))."))

```

which was adapted from the StaticArrays version [here](https://github.com/JuliaArrays/StaticArrays.jl/blob/59f92e0ca7ac391a850a6e7a2ce1eb53aa237fc4/src/convert.jl#L1):

```julia
julia> Point{2,Int}((3,3,3))
ERROR: DimensionMismatch("No precise constructor for Point{2,Int64} found. Length of input was 3.")
...

```

Edit: or better yet

```julia
Point{Dim1,T}(coords::NTuple{Dim2}) where {T,Dim1,Dim2} = 
    throw(DimensionMismatch("Can't construct a Point{$(Dim1),$(T)} with an input of length $(Dim2)"))

```

```julia
julia> Point{1,Int}((3,3,3))
ERROR: DimensionMismatch("Can't construct a Point{1,Int64} with an input of length 3")

```

which is even clearer what it does in the code.

---

<div class="post-metadata">

### Author: ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)
#### Post date: [February 4, 2021, 9:27pm UTC](https://discourse.julialang.org/t/ambiguity-with-vararg-outer-constructor-and-inner-constructor/54622/8 "2021-02-04T21:27:06Z")

</div>

Thank you all, I am a bit confused with the snippets of code. Did we arrive at a final solution? What is the modification needed compared to the original code?

---

<div class="post-metadata">

### Author: ![FPGro](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fpgro/32/20822_2.png) [@FPGro](https://discourse.julialang.org/u/FPGro)
#### Post date: [February 4, 2021, 10:00pm UTC](https://discourse.julialang.org/t/ambiguity-with-vararg-outer-constructor-and-inner-constructor/54622/9 "2021-02-04T22:00:18Z")

</div>

Almost, I did just add to the PR. However, the constructors now fail for mixed input types only. This is seriously weird, but I’ll investigate:

```julia
julia> Point{2,Float64}(0,0)
Point{2, Float64}([0.0, 0.0])

julia> Point{2,Float64}(0.,0)
ERROR: DimensionMismatch("Can't construct a Point{2,Float64} with an input of length 1")
Stacktrace:
 [1] Point{2, Float64}(coords::Tuple{Tuple{Float64, Int64}})
   @ Main ~\Desktop\meshes.jl:17
 [2] Point{2, Float64}(coords::Tuple{Float64, Int64})
   @ Main ~\Desktop\meshes.jl:10
 [3] top-level scope
   @ REPL[15]:1

julia> Point{2,Float64}((0.,0))
ERROR: DimensionMismatch("Can't construct a Point{2,Float64} with an input of length 1")
Stacktrace:

```
