not logged in | [Login]
Parameterized types is a feature of the LuaJIT FFI. It enables one to specify FFI C declarations dynamically. It is useful for generic programming, similar to why one might use C++ templates or Java generics.
This feature was introduced in June 2012 and is available in LuaJIT version 2.0.1 and later.
To use this feature, you can place the $
character in a C type declaration, used by ffi.cdef
and ffi.type
. When you use this declaration, you pass arguments along with it. These arguments can be a ctype/cdata, a string or a number.
Here are some examples (which in isolation are not very interesting):
ffi.typeof("$ *", foo_t)
ffi.typeof("struct { $ k; $ v; }[?]", foo_t, bar_t)
ffi.typeof("uint8_t[$][$]", width, height)
The following was posted on the LuaJIT mailing list.
The $
character is the marker to be replaced with parameters in a C type declaration:
A ctype or a cdata parameter is interpreted like an anonymous
typedef. You can use that to construct derived types, e.g.
ffi.typeof("$*", ct)
is a pointer to ct
, or "$[10]"
is a 10
element array. Unlike simplistic string concatenation, this
works for all cases (e.g. pointers to function pointers).
A string parameter is treated like an identifier or keyword, except the string is not parsed again or split into words (this isn't simple textual substitution). You can use that for field names, function names, argument names etc.
[I'm pondering whether I should drop the keyword and type lookup on the string. That would allow you to use arbitrary names for fields, even when they collide with keywords or typedefs. I may change that before writing the docs.]
A number parameter is treated as an integer. You can use that to construct fixed size arrays. In some contexts you may prefer this over VLAs. It works for multi-dimensional types, too. E.g.:
local matrix_t = ffi.typeof("uint8_t[$][$]", width, height)
...
local m = matrix_t()
Only ffi.typeof()
and ffi.cdef()
parse parameterized types. The
other functions, e.g. ffi.new()
, don't do that, because they
already take other arguments (e.g. initializers). I guess it would
be too confusing to mix two kinds of arguments.
Anyway, parameterized types are a nice tool, but you'll want to use it sparingly!
The typical scenarios where you'd use templates in C++ come to mind: e.g. construct a stack or a queue of an arbitrary type. These need to create an array of the element type, which is now really easy to do. [Justin's use case]
Another example are derived types of anonymous structs. This avoids pollution of the struct namespace. [Henk's use case]
Mike posted this example on the LuaJIT mailing list:
local ffi = require("ffi")
local function stack_iter(stack)
local top = stack.top
if top > 0 then
stack.top = top-1
return stack.slot[top-1]
end
end
local stack_mt = {
__new = function(tp, max)
return ffi.new(tp, max, 0, max)
end,
__index = {
push = function(stack, val)
local top = stack.top
if top >= stack.max then error("stack overflow") end
stack.top = top + 1
stack.slot[top] = val
end,
pop = function(stack)
local top = stack.top
if top <= 0 then error("stack underflow") end
stack.top = top-1
return stack.slot[top-1]
end,
iter = function(stack)
return stack_iter, stack
end,
}
}
local function makestack(ct)
local tp = ffi.typeof("struct { int top, max; $ slot[?]; }", ct)
return ffi.metatype(tp, stack_mt)
end
local stack_t = makestack(ffi.typeof("double"))
local stack = stack_t(100)
for i=1,100 do stack:push(i) end
for x in stack:iter() do io.write(x, " ") end
io.write("\n")
The following libraries use Parameterized Types: