# When to use promote on parametric constructors

I’m #newtojulia and working on converting a C++ project (hence my habit to type everything). I’m trying to be flexible and using parametric composite types (see below) and bumped into the common error of “no method matching…”. The docs outline that I can use `promote()` `convert()` to handle my situation, but I’m wondering if that is the “right way” OR am I artificially restricting myself by using a parametric constructor?

If this is a “it depends” answer, then when would I use `promote` `convert` and when would I not use a parametric constructor?

Thanks!

MWE (Edit to make it more accurate to my exact problem):

``````struct MyStruct{T<:AbstractFloat}
x::Vector{T}
y::Vector{T}
z::T

function MyStruct{T}(x::Vector{T}, y::Vector{T}, z::T) where T<:AbstractFloat
new(x, y, z)
end
end

xx = [1.0, 2.0, 3.0] #Vector{Float64}
yy = [4.0, 5.0, 6.0] #Vector{Float64}
zz = 1 # Integer
m1 = MyStruct(xx, yy, zz) # MethodError: no method matching MyStruct(::Vector{Float64}, ::Vector{Float64}, ::Int64)
``````

which can be fixed by

``````MyStruct(x::Vector{T}, y::Vector{T}, z::Real) where T<:AbstractFloat = MyStruct{T}(x, y, T(z))
``````

Must your elements be `<:AbstractFloat`, or could they be `<:Real`? In your example, `promote` will not convert the `Int`s to anything that `<:AbstractFloat`. You would need to `convert` instead.

``````julia> promote(1,2,3)
(1, 2, 3)

julia> typeof(ans)
Tuple{Int64, Int64, Int64}

julia> convert(AbstractFloat, 5)
5.0
``````

Converting may make it more ergonomic, but it might mask errors, that part ‘it depends’. Since you’re coming from C++ where implicit conversion between numeric types is common, I would think you’re likely opt for converting. Julia allows mixed int and float expressions, but it is also strict about not promoting int arguments to match a float argument. That makes it hard to infer what expected behavior is, so that probably motivates your question.

1 Like

Indeed I actually did just implement a convenience constructor using `convert` xD Actually I realize now that my MWE is a little misconstrued and I think I’ll go back and edit it to reflect a better reality.

They could certainly be `<:Real`, but they’ll almost guaranteed be used in floating point math, hence my intuition to just type them that way. The Julia docs do say that typing everything isn’t the way to go to make things faster though.

You’re getting at the heart of my problem, which is trying to do things the Julian way, and if keeping things `Real` and letting the compiler figure it all out then that’s fine with me.

Here’s just me slapping away at the REPL for a minute:

``````julia> struct TestStruct{T}; x::T; y::T; z::T; end

julia> TestStruct(1,2.0,3//1) # mixed types do not work
ERROR: MethodError: no method matching TestStruct(::Int64, ::Float64, ::Rational{Int64})
Closest candidates are:
TestStruct(::T, ::T, ::T) where T at REPL[487]:1
Stacktrace:
[1] top-level scope
@ REPL[488]:1

julia> methods(TestStruct) # see that the default constructor requires all arguments of matched type
# 1 method for type constructor:
[1] TestStruct(x::T, y::T, z::T) where T in Main at REPL[487]:1

julia> TestStruct(x,y,z) = TestStruct(promote(x,y,z)...) # define a promoting constructor
TestStruct

julia> TestStruct(1,2.0,3//1)
TestStruct{Float64}(1.0, 2.0, 3.0)
``````

Note that I didn’t use an explicit inner constructor in this example. The default inner constructor was good enough for me. In your MWE it should be too, although perhaps in your example you need a little more. Inner constructors are mostly useful for creating invariants (for example enforcing that `x>=0`).

I also didn’t bother to constrain the parameter `T`, although constraining it to `Real` or `Number` would be very reasonable. `AbstractFloat` is something that should rarely be necessary (it’s mostly used for dispatch on basic numerical functions).

EDIT: my response doesn’t look especially useful to your revised MWE.

2 Likes

Another option is to just include the `.0` to explicitly make the value a float, which I try to be in the habit of anyway with C++ since I’ve been bit by the compiler not implicitly converting when I thought it would (e.g. integer division). This might just be the most clear solution.

1 Like

But it was still helpful, in particular the bit about not using `<:AbstractFloat`. Thanks!