How to use `@kwdef` with parametric types and inner constructors

I am having problems declaring a parametric structure with @kwdef and an inner constructor to perform some checks.

My struct has many parameters and complicated checks, but this simple example is enough to explain my problems:

@kwdef struct B{T <: Number}
    x::T = zero(T)
    B(x) = begin
        (x < zero(x)) && throw(DomainError("No negative values!"))
        new{typeof(x)}(x)
    end
end

This works for all the case of interest but the last one:

julia> B(0)
B{Int64}(0)

julia> B(0.0)
B{Float64}(0.0)

julia> B(x = 0)
B{Int64}(0)

julia> B(x = 0.0)
B{Float64}(0.0)

julia> B{Int}()
ERROR: MethodError: no method matching B{Int64}(::Int64)

Closest candidates are:
  B{T}(; x) where T<:Number
   @ Main util.jl:579

Stacktrace:
 [1] B{Int64}(; x::Int64)
   @ Main ./util.jl:579
 [2] B{Int64}()
   @ Main ./util.jl:579
 [3] top-level scope
   @ REPL[50]:1

I tried to explore what @kwdef is doing using @macroexpand, but I find nothing strange: in the definition of B I see one inner constructor (the one I defined) and two outer constructors defined by @kwdef, including B{T}(; x = zero(T)) where T <: Number: I cannot understand why this is not called by B{Int}().

May somebody explain me why B{Int}() is incorrect, please? What would be the best way to construct B without specifying any parameter but the type T?

Your inner construct needs to be parameteric too:

@kwdef struct B{T <: Number}
    x::T = zero(T)
    B{T}(x::T) where T = begin
        (x < zero(x)) && throw(DomainError("No negative values!"))
        new{T}(x)
    end
end

The reason is that when you define an inner constructor, you ‘delete’ all other default inner ones too, which includes B{T}(x::T). But the latter is required by one of the outer constructors that is generated with @kwdef (from @macroexpand @kwdef ...)

 (B{T}(; x = zero(T)) where T <: Number) = begin
                #= util.jl:579 =#
                B{T}(x)
            end
1 Like

Oh, I see. I was under the wrong impression that Julia was missing the presence of the outer constructor. Thanks a lot!