One of the cool features introduced in the C++11 standard is the ability to declare a template parameter as being variadic; i.e. that parameter represents a sequence of an arbitrary number of arguments (including zero!).

For example, suppose the parameter pack args expands contains the sequence A0, A1, A2. To expand the pack, an expression is followed using the expansion operator ..., and the expression is repeated for each argument with the parameter pack replaced by each element of the pack.

Some example expansions (assume f and g are not variadic packs):

args... -> A0, A1, A2
f(args)... -> f(A0), f(A1), f(A2)
args(f,g)... -> A0(f,g), A1(f,g), A2(f,g)

If the expansion operator is used on an expression which contains multiple un-expanded parameter packs, the packs are expanded in lock-step. Note that it is required that all parameter packs which participate in a single expansion operator must all have the same length.

For example, consider the packs args0 -> A0, A1, A2 and args1 -> B0, B1, B2. Then the following expressions expand to:

f(args0, args1)... -> f(A0, B0), f(A1, B1), f(A2, B2)
g(args0..., args1)... -> g(A0, A1, A2, B0), g(A0, A1, A2, B1), g(A0, A1, A2, B2)

The goal of this challenge is given an expression and set of parameter packs, produce the expanded equivalent.

Note that there must be at least one un-expanded parameter pack as the argument to the expansion operator. All of the following are syntax errors (assume args0 = A0, A1, A2, args1 = B0, B1):

f(args0, args1)... // packs must have the same length
f(c)... // no un-expanded packs in expansion operator
f(args0...)... // no un-expanded packs in expansion operator

Your program needs not handle any expressions which result in a syntax error.


Your program/function should take as input a dictionary-like object which associates a parameter pack name with it's arguments, and an expression to be expanded (expression syntax explained later). For example, in Python-like syntax the dictionary of packs might look something like this:

{'args0': ['A0', 'A1', 'A2'],
 'args1': [],
 'args2': ['B0', 'A1', 'B2']}

Feel free to adapt the parameter pack input dictionary as desired. For example, it could be a list of strings where each string is a space-separated list and the first value is the pack name:

args0 A0 A1 A2
args2 B0 A1 B2

Expressions to be expanded will contain only valid C++ identifiers (case-sensitive alpha-numerics and underscore), function call operators, parenthesis grouping, commas, and the expansion operator. Any identifiers in the expression which are not in the dictionary of parameter packs are not variadic. You may assume the given expression has a valid expansion. It is your decision on what restrictions there are on how whitespace is used (for example, every token has a space between it and other tokens, or no whitespace at all).

The input may come from any source desired (stdin, function parameter, etc.)


The output of your program/function is a single string representing the expanded expression. You are allowed to remove/add any whitespace or parenthesis grouping so long as the expression is equivalent. For example, the expression f(a,b,c,d) is equivalent to all of the following:

f ( a,  b, c , d )
f((a), (b), (c), (d))

The output can be to any source desired(stdout, function return value, etc.)


All examples are formatted as such:

The dictionary of packs are expressed as lines of strings in the example format given above (space separated, first value is the pack name), followed by the expression, and the expected result.

case 1:

args0 A0 
args2 B0 A0

case 2:

args0 A0 
args2 B0 A0
// nothing

case 3:

args0 A0 
args2 B0 A0
// nothing

case 4:

args0 A0 
args2 B0 A0

case 5:

args0 A0 
args2 B0 A0
f(C0, args0..., args2)...
f(C0, A0, B0), f(C0, A0, A0)

case 6:

args0 A0 A1
args1 B0 B1
args2 C0 C1
f(args0, args1, args2)...
f(A0, B0, C0), f(A1, B1, C1)

case 7:

args0 A0 A1
A0(C0), A1(C0)

case 8:

args0 A0 A1
args1 B0 B1
A0(B0(f)), A1(B1(f))

case 9:

args0 A0 A1
args1 B0 B1

case 10:

args1 A0 A1
f(args0..., args1)...
f(A0), f(A1)


This is code-golf; shortest code in bytes wins. Standard loop-holes apply. You may use any built-ins desired.

  • \$\begingroup\$ Do we need to handle the case where an empty parameter pack is part of a larger comma-separated list? Example: with args empty, f(a, args...) -> f(a) or f(args..., b) -> f(b) \$\endgroup\$ – feersum Sep 17 '16 at 19:18
  • \$\begingroup\$ @feersum yes, I added case 10 to test something similar to that case \$\endgroup\$ – helloworld922 Sep 17 '16 at 19:21
  • 3
    \$\begingroup\$ Happy 1000 rep! \$\endgroup\$ – Conor O'Brien Sep 18 '16 at 1:32

Python 3, 567 bytes

lambda v,q:w(g(v,p(q),-1))
def c(s):
	for x in s:
		if x!=","or b:l+=x;b+=~-")(".find(x)%3-1
	return a+[p(l)]
p=lambda s:(0,p(s[:-3]))if"..."==s[-3:]else(1,(2,s.split("(")[0]),c(s[s.find("(")+1:-1]))if")"==s[-1]else(2,s)
def g(v,q,i):
	if q[0]>1:return q[1]if q[1]not in v or i<0else v[q[1]][i]
	if q[0]:return(g(v,q[1],i),[g(v,k,i)for k in q[2]])
	while 1:
		except:return a
w=lambda k:",".join(q for q in[w(x)for x in k]if q)if type(k)==list else k if type(k)==str else k[0]+"("+w(k[1])+")"

Try it online!

Could use a lot of golfing but I'm pretty happy with this answer. Tested on all 10 example cases.

-9 bytes thanks to Neil

  • \$\begingroup\$ I'm convinced there's some way to golf [-1,1,0]["()".find(x)] but I can't find it :/ \$\endgroup\$ – caird coinheringaahing Oct 8 '20 at 16:14
  • 1
    \$\begingroup\$ @cairdcoinheringaahing I got -2 bytes with -~-~"()".find(x)%3-1 :P \$\endgroup\$ – hyper-neutrino Oct 8 '20 at 16:21
  • \$\begingroup\$ What are the possible values of x and the desired value in each case? \$\endgroup\$ – Neil Oct 8 '20 at 17:41
  • \$\begingroup\$ @Neil I want "(" to give -1, ")" to give 1, and anything else to give 0 (it's my bracket balance count for splitting on commas). \$\endgroup\$ – hyper-neutrino Oct 8 '20 at 17:43
  • \$\begingroup\$ In that case, does ~-")(".find(x)%3-1 work? \$\endgroup\$ – Neil Oct 8 '20 at 17:49

Wolfram Language (Mathematica), 267 246 bytes


Try it online!

As far as I can tell there's no built-in way to Thread beyond the first level (or Thread heads, for that matter).

a=""                                        (* ensures Reverse[R,3] works properly  *)
R=StringReplace@{                           (* parses input: *)
  "("->"[",")"->"]",                        (*  convert brackets to mathematica-style *)
  "..."->"!",                               (*  `...` is costly to manipulate, so instead use something that isn't. *)
  a:(WordCharacter|"_")..:>"\""<>a<>"\""};  (*  wrap symbols in quotes so they won't evaluate. *)
a@ToExpression@R@#                          (* parse input and wrap it with "" to allow multiple outputs *)
 //.a_String@b___:>a~q~b                    (* convert expressions to a form suitable for calling Thread *)
  /.p@@@#                                   (* substitute packed arguments *)
 //.a_?(FreeQ[_!])!]:>                      (* expand ...s from the bottom up: *)
   (a//.s_/;FreeQ[s,p,{3,∞}]:>s~Thread~p    (*  thread over packed arguments from the bottom up, *)
      /.p->(##&))                           (*  and unpack into sequences. *)
  /.q->Construct                            (* convert back into an expression *)
Reverse[R,3]@ToString[ % ]                  (* convert to c-style output. *)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.