Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expression contains unexpanded parameter packs

Somehow I don't get how variadic template parameter packs are expanded. What's wrong with thie following code?

#include <iostream>

template <typename T>
struct print_one
{
    static void run(const T& t)
    {
        std::cout << t << ' ';
    }
};

template<typename... Args>
void print_all(Args&&... args)
{
    // the next line doesn't compile:
    print_one<Args>::run(std::forward<Args>(args))...;
}

int main()
{
    print_all(1.23, "foo");
}

Clang says, Expression contains unexpanded parameter packs 'Args' and 'args'. Why?

like image 425
marton78 Avatar asked Sep 20 '12 15:09

marton78


People also ask

What is parameter pack in c++?

Parameter packs (C++11) A parameter pack can be a type of parameter for templates. Unlike previous parameters, which can only bind to a single argument, a parameter pack can pack multiple parameters into a single parameter by placing an ellipsis to the left of the parameter name.

What is Variadic template in C++?

Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration. However, variadic templates help to overcome this issue.

What is Pack expansion?

Pack expansion A pattern followed by an ellipsis, in which the name of at least one parameter pack appears at least once, is expanded into zero or more comma-separated instantiations of the pattern, where the name of the parameter pack is replaced by each of the elements from the pack, in order. template<class...


2 Answers

The ... has to go inside the function call parentheses:

print_one<Args>::run(std::forward<Args>(args)...);

Obviously, that won't work for your function that takes only a single argument, so you need to find a way to expand the calls into a function call or other allowed construct:

// constructing a dummy array via uniform initialization
// the extra 0 at the start is to make it work when the pack is empty
int dummy[]{0, (print_one<Args>::run(std::forward<Args>(args)), 0)...};

// or, if your compiler doesn't support uniform initialization
int dummy[] = {0, (print_one<Args>::run(std::forward<Args>(args)), 0)...};

// or, calling a dummy function
template<typename... Args> void dummy(Args...) {}
dummy((print_one<Args>::run(std::forward<Args>(args)), 0)...);

// or, constructing a temporary dummy object
struct dummy { dummy(std::initializer_list<int>) {} };
dummy{(print_one<Args>::run(std::forward<Args>(args)), 0)...};

// or, constructing a temporary initializer list
std::initializer_list<int>{(print_one<Args>::run(std::forward<Args>(args)), 0)...};

Note the use of the comma operator to turn the void return of print_one into a value suitable to place in an argument list or initializer expression.

The initializer-list forms are preferred to the function call forms, as they are (supposed to be) ordered LTR which function call arguments are not.

The forms where a parameter pack expansion can occur are covered by 14.5.3 [temp.variadic]:

4 - [...] Pack expansions can occur in the following contexts:

  • [...]

Your original code is illegal because although textually it might appear that it should produce a statement consisting of a number of comma-operator expressions, that is not a context allowed by 14.5.3:4.

like image 57
ecatmur Avatar answered Sep 16 '22 11:09

ecatmur


The standard dictates where pack expansion is allowed:

§14.5.3 [temp.variadic] p4

[...] Pack expansions can occur in the following contexts:

  • In a function parameter pack (8.3.5); the pattern is the parameter-declaration without the ellipsis.
  • In a template parameter pack that is a pack expansion (14.1):
    • if the template parameter pack is a parameter-declaration; the pattern is the parameter-declaration without the ellipsis;
    • if the template parameter pack is a type-parameter with a template-parameter-list; the pattern is the corresponding type-parameter without the ellipsis.
  • In an initializer-list (8.5); the pattern is an initializer-clause.
  • In a base-specifier-list (Clause 10); the pattern is a base-specifier.
  • In a mem-initializer-list (12.6.2); the pattern is a mem-initializer.
  • In a template-argument-list (14.3); the pattern is a template-argument.
  • In a dynamic-exception-specification (15.4); the pattern is a type-id.
  • In an attribute-list (7.6.1); the pattern is an attribute.
  • In an alignment-specifier (7.6.2); the pattern is the alignment-specifier without the ellipsis.
  • In a capture-list (5.1.2); the pattern is a capture.
  • In a sizeof... expression (5.3.3); the pattern is an identifier.

So basically, as a top-level statement, expansion is not allowed. The rationale behind this? No idea. Most likely they only picked contexts where a seperating comma (,) is part of the grammar; anywhere else you might pick overloaded operator, if user-defined types are involved and get in trouble.

like image 29
Xeo Avatar answered Sep 20 '22 11:09

Xeo