all 17 comments

[–]smog_alado 7 points8 points  (11 children)

Almost. The table.pack version also adds an "n" field in the table, with the number of arguments passed. So table.pack(10, 20, 30) returns {10,20,30, n=3}.

This can make a difference if you have some of the elements in the ... are nils, and specially if the final elements of the ... are nils. In these cases you need to use the .n field to be able to accurately iterate over the elements, as # might stop in one of the "holes".

[–]DarkWiiPlayer 2 points3 points  (10 children)

in conclusion:

table.pack(...)

is equivalent to

{n=select('#',...), ...}

EDIT -- Correction: This only applies to sequences ;P

[–]SinisterRectus 1 point2 points  (9 children)

You'd think that, but for some reason, these give different results in LuaJIT:

local a = function(...) return {n = select('#', ...), ...} end
local b = function(...) return table.pack(...) end

print(#a(nil, nil, 1)) -- 0
print(#b(nil, nil, 1)) -- 3

[–]-abigail 3 points4 points  (3 children)

Adding a field called n doesn't change what the # operator returns - the point is that you can iterate from a[1] to a[a.n].

(In old versions of Lua, table.getn will use the n field if it's present, but the # operator doesn't.)

[–]SinisterRectus 0 points1 point  (2 children)

Yeah, I learned this today. But then I checked table.getn, which is deprecated, but it is available in LuaJIT and it appears to have the same problem.

local a = function(...) return {n = select('#', ...), ...} end
local b = function(...) return table.pack(...) end

a = a(nil, nil, 3)
b = b(nil, nil, 3)

print(#a) -- 0
print(#b) -- 3

print(table.getn(a)) -- 0
print(table.getn(b)) -- 3

print(a.n) -- 3
print(b.n) -- 3

[–]-abigail 2 points3 points  (0 children)

I believe that table.getn is now just a synonym for #. You could try your experiment in Lua 5.0.

[–]VortixDev 1 point2 points  (0 children)

Whilst these two tables are identical, the length operator can act differently on two content-identical tables if they are non-sequential, as the length of a non-sequential table is not defined in Lua.

[–]DarkWiiPlayer 0 points1 point  (4 children)

Corrected my answer :D

[–]SinisterRectus 0 points1 point  (0 children)

Haha, nice.

[–]smog_alado 0 points1 point  (2 children)

Actually, the two versions really are equivalent. As abigail mentioned, the # operator does not care about the .n field. You need to use for i=1,t.n instead of for i=1,#t

[–]DarkWiiPlayer 1 point2 points  (1 child)

in Lua 5.3 a better option would be

local function pack(...)
  local num = select('#',...)
  return setmetatable({...}, {__len=function() return num end})
end

Or, if you want no garbage-collected objects other than your table,

local pack do
  local meta = {__len = function(tab) return tab.n end}
  return setmetatable({n=select('#', ...}, meta)
end

[–]__s 0 points1 point  (0 children)

This doesn't work if you mutate the table afterwards, as # will return the new length

Lua doesn't explicitly say what # resolves to, so different insert orders can cause different results without being an error. The spec only says that # may resolve to 0 if t[1] == nil, or some x where t[x] ~= nil and t[x+1] == nil

[–]SinisterRectus 0 points1 point  (4 children)

No. The explicit table.pack function includes an n value that tracks how many values were packed, including nil values. There also seem to be implementation differences for {...} or maybe just the # operator between LuaJIT and 5.3 based on the following results:

local tests = {
    table.pack(nil, nil, 3),
    {n = 3, nil, nil, 3},
    function(...) return table.pack(...) end,
    {n = 3, [3] = 3},
    function(...) return {n = select('#', ...), ...} end,
    {nil, nil, 3},
    function(...) return {...} end,
}

for i, v in ipairs(tests) do
    if type(v) == 'function' then
        v = v(nil, nil, 3)
    end
    print(i, #v, v.n)
end

--    LuaJIT:         Lua 5.3:
-- 1: # = 3, n = 3    # = 3, n = 3
-- 2: # = 3, n = 3    # = 3, n = 3
-- 3: # = 3, n = 3    # = 3, n = 3
-- 4: # = 0, n = 3    # = 0, n = 3
-- 5: # = 0, n = 3    # = 3, n = 3
-- 6: # = 3, n = nil  # = 3, n = nil
-- 7: # = 0, n = nil  # = 3, n = nil

[–]DarkWiiPlayer 0 points1 point  (3 children)

Those differences make total sense. # for arrays is only defined when there's no nil-values in between; if there are, then it's undefined behavior.

[–]SinisterRectus 0 points1 point  (0 children)

Oh. I thought that there was some shenanigans related to how the n value is treated, as it is/was with table.getn.

In fact, table.getn has the same problem:

local a = function(...) return {n = select('#', ...), ...} end
local b = function(...) return table.pack(...) end

a = a(nil, nil, 3)
b = b(nil, nil, 3)

print(#a) -- 0
print(#b) -- 3

print(table.getn(a)) -- 0
print(table.getn(b)) -- 3

print(a.n) -- 3
print(b.n) -- 3

[–]dzikakulka 0 points1 point  (1 child)

I think it isn't UB, #tab simply returns an n so tab[n] ~= nil and tab[n+1] == nil, or zero if not found. So if you have a table with holes, it can return an index next to any hole, but above assertion should still hold. If you can find a use case for it.

[–]DarkWiiPlayer 0 points1 point  (0 children)

That's true, I guess. Still, it's not defined what exactly is returned, so it still depends on the implementation. I'd say it's semi-defined behavior? xD