Tuples of CartesianIndex and broadcasting

While I can broadcast scalars, like

julia> 1 .+ (2, 3)
(3, 4)

I can’t do the same with CartesianIndexs:

julia> CartesianIndex(1,1) .+ (CartesianIndex(2,2), CartesianIndex(3,3))
ERROR: iteration is deliberately unsupported for CartesianIndex. Use `I` rather than `I...`, or use `Tuple(I)...`
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] iterate(::CartesianIndex{2}) at ./multidimensional.jl:166
 [3] copyto!(::Array{Int64,1}, ::CartesianIndex{2}) at ./abstractarray.jl:733
 [4] _collect(::UnitRange{Int64}, ::CartesianIndex{2}, ::Base.HasEltype, ::Base.HasLength) at ./array.jl:630
 [5] collect(::CartesianIndex{2}) at ./array.jl:624
 [6] broadcastable(::CartesianIndex{2}) at ./broadcast.jl:682
 [7] broadcasted(::Function, ::CartesianIndex{2}, ::Tuple{CartesianIndex{2},CartesianIndex{2}}) at ./broadcast.jl:1260
 [8] top-level scope at REPL[15]:1

I understand that 1 .+ CartesianIndex(1,1) fails, but since CartesianIndex(1,1) + CartesianIndex(1,1) is fine, I would expect the above example to work. Is the behavior intended, or a bug?

(I can of course write

julia> (CartesianIndex(1,1),) .+ (CartesianIndex(2,2), CartesianIndex(3,3))
(CartesianIndex(3, 3), CartesianIndex(4, 4))

instead. But that’s less elegant… and beside the point.)

I think the problem there is that there is a possible ambiguity on whether the broadcast is over the indexes of the tuple or the indexes of the CartesianIndex.

Hmm… But since broadcasting over the indexes of the CartesianIndex is not allowed (1 .+ CartesianIndex(1,1) errors), I would assume that we are left with only one possibility, no?

You can write Ref(CartesianIndex(1,1)) .+ stuff to protect it from the broadcast. (Similar to (... ,), as I see now you said.) I think the way to specify that a type is always scalar with respect to broadcasting is

Broadcast.broadcastable(I::CartesianIndex) = Ref(I)

I wonder if it would break anything to define this?

We’ve been hesitant to make CartesianIndexes collection-like (that is, iterable and broadcastable) because their reason for being is the they represent a single location. Generally I’ll just convert them to Tuples when I want to treat them as a list of indices.

Maybe I’m misunderstanding, but I’m arguing for the opposite, namely to treat CartesianIndexes as “single locations”/scalars “all the way” (i.e. not collection-like). If that were the case, my example above would not fail…

Oh, you’re absolutely right! 90% of the requests we get for this are the other way, and I simply didn’t read closely enough.

Yes, I think making it broadcast like a scalar would be reasonable — and nonbreaking since it errors now.

Well, I’m a 100% with the 10% then :smile:

Should I file an issue?

Please do!

Done