Defining custom subtype of AbstractArray


#1

I am having some trouble defining my struct as a subtype of AbstractArray.

As an example, say I want to define a Point in dim dimensional space as follows:

struct Point{dim} <: AbstractArray{Float64,dim}
	x::NTuple{dim, Float64}
end

Now according to the documentation, the minimum functions I need to overload for Point to be an AbstractArray are: Base.size, Base.IndexStyle, Base.getindex and Base.setindex!

So I do it as follows:

Base.size(p::Point{dim}) where dim = (dim,)
Base.IndexStyle(::Type{<:Point}) = IndexLinear()
Base.getindex(p::Point{dim}, i::Int64) where dim = p.x[i]
Base.setindex!(p::Point{dim}, v, i::Int64) where dim = (p.x[i] = v)

If I try to initialize this struct, I get an error message that I can’t quite understand:

julia> Point((1.5,2.4))
2-element Point{2}:Error showing value of type Point{2}:
ERROR: BoundsError: attempt to access (Base.OneTo(2),)
  at index [2]
Stacktrace:
 [1] getindex(::Tuple{Base.OneTo{Int64}}, ::Int64) at ./tuple.jl:24
 [2] axes at ./abstractarray.jl:57 [inlined]
...

I would greatly appreciate inputs on the correct way to do this.

Thanks!


#2

Your trouble is stemming from the fact that you’re reporting to AbstractArray that your struct has dim dimensions when it’s only 1-dimensional. Change it to struct Point{dim} <: AbstractArray{Float64,1} and I think all should work. I’d probably rename dim to len to make it clearer.

Oh, not quite all will work: Your setindex! method will run into trouble since tuples are immutable, so if you use a tuple as your backing you need to make the strict itself mutable and replace the entire tuple when you change it.


#3

Also for generality you could do:

struct Point{T,dim} <: AbstractArray{T,1}
    x::NTuple{dim, T}
end

So its not limited to Float64


#4

You’ve actually re-created the bug in the original post. You want <: AbstractArray{T, 1}, not <: AbstractArray{T, dim}.


#5

Oops I thought that looked weird! mbaumans code change is kinda hard to read in inline quotes :wink:


#6

If you make it mutable Point{dim, T} <: AbstractVector{T} (here AbstractVector{T} is shorthand for AbstractArray{T, 1}), then consider

@inline Base.setindex!(p::Point{dim, T}, v, i::Int64) where {dim, T}
    @boundscheck checkbounds(p, i)
    if isbitstype(T)
        GC.@preserve p Base.unsafe_store!(convert(Ptr{T}, pointer_from_objref(p)), convert(T, v), i)
        return v
    else
        vv = convert(T, v)
        p.x = ntuple( (n-> (n===i ? vv : @inbounds p.x[n]) ), dim)
        return v
    end
end

This is what StaticArrays does for MVector.