Related
Having following functions f0, f1, f2 in C++14 code, which accepts arbitrary number of fixed-length arrays:
#include <functional>
template<typename... TS, size_t N> void f0( TS(&& ... args)[N] ) {}
template<typename T, size_t... NS> void f1( T(&& ... args)[NS] ) {}
template<typename... TS, size_t... NS> void f2( TS(&& ... args)[NS] ) {}
int main(){
f0({1,2}, {3.0,4.0}, {true, false});
f1({1,2,3}, {4,5}, {6});
f2({1,2,3}, {4.0,5.0}, {true});
return 0;
}
Function f0 accepts arrays with different types and fixed array length. Function f1 accepts arrays with fixed type and different array lengths. It's clear how this works: C++ compiler deduces variable-length parameter pack in immediate context of template function instantiation, which is expanded in (&& ... args) expression.
Function f2 accepts arrays with different types and different array lengths, which produces two variable-length parameter packs, however there is only one ellipsis operator in pack expansion (&& ... args), but code compiles and works well.
So question is: what is general rule for expanding multiple parameter packs within single ellipsis operator? Obviously, at a minimum, they must be the same length, but what are the other requirements? Is there a precise definition that the n-th element of the first parameter packing should expand along with the n-th element of the second parameter packing?
Also, following code with explicit template argument provision does not compile: f2<int,float,bool,3,2,1>({1,2,3},{4.0f,5.0f},{true});. It would be interesting to know the reasons for this behaviour.
This is specified in C++ Standard section [temp.variadic]. Basically, it's what you described: when a pack expansion expands more than one pack, all those packs must have the same number of elements. And the expansion in most cases forms a list where the nth element in the resulting list uses the nth element of each expanded pack.
More exactly, paragraph 5 defines
A pack expansion consists of a pattern and an ellipsis, the instantiation of which produces zero or more instantiations of the pattern in a list (described below). The form of the pattern depends on the context in which the expansion occurs. Pack expansions can occur in the following contexts:
In a function parameter pack; the pattern is the parameter-declaration without the ellipsis.
...
In your example, each function template declares a function parameter pack named args. The patterns are TS(&& args)[N], T(&& args)[NS], and TS(&& args)[NS].
Paragraph 7 (after clarifying which packs are expanded by which pack expansions that when one pack expansion appears inside another) has the requirement
All of the packs expanded by a pack expansion shall have the same number of arguments specified.
And paragraph 8:
The instantiation of a pack expansion that is neither a sizeof... expression nor a fold-expression produces a list of elements E1, E2, ..., EN, where N is the number of elements in the pack expansion parameters. Each Ei is generated by instantiating the pattern and replacing each pack expansion parameter with its ith element.
So yes, for the instantiation of f3 where TS is deduced as int, double, bool and NS is deduced as 3, 2, 1, the pack expansion becomes a function parameter list with types int(&&)[3], double(&&)[2], bool(&&)[1].
All packs appearing as part of one pack expansion (...) must have exactly the same length. Otherwise substitution fails (which depending on context is a hard error or SFINAE). (see [temp.variadic]/7)
All packs are expanded so that the i-th expanded element of the pack expansion uses the i-th element of each pack. For the detailed expansion rule see [temp.variadic]/8.
(Links are to the post-C++20 draft of the standard, but the same applies to C++14.)
A template parameter pack is a template parameter that accepts zero or more template arguments (non-types, types, or templates). A function parameter pack is a function parameter that accepts zero or more function arguments.
A template with at least one parameter pack is called a variadic template.
Visit[1]: https://en.cppreference.com/w/cpp/language/parameter_pack
Visit[2]: Multiple expansions of multiple parameter packs in the same expression
Visit[3]: https://www.ibm.com/docs/en/zos/2.1.0?topic=only-variadic-templates-c11
I hope this help you.
The function std::forward is declared like so:
template< class T >
T&& forward( typename std::remove_reference<T>::type& t ) noexcept;
Meaning it takes a single argument of type std::remove_reference<T>::type&.
However, std::forward can also be used with variadic arguments:
template <typename... Args>
void f(Args... args)
{
q(std::forward<Args>(args)...);
}
I understand variadic templates and unpacking them.
However, I don't understand the syntax of ... coming "after " the call to std::forward.
What does this syntax mean precisely? Why does it work? Can it be used with functions other than std::forward?
This is what is called pack expansion. It expands the parameter pack out with commas between each member of the pack so
q(std::forward<Args>(args)...);
says that for each member of the parameter pack args apply std::forward to it. The resulting code would look like
q(std::forward<Args>(args1), std::forward<Args>(args2), ..., std::forward<Args>(argsN));
This works with any expression, it doesn't just apply to std::forward. For instance you could add some value to each parameter like
q((args + 1)...);
and that would expand to
q((args1 + 1), (args2 + 1), ..., (argsN + 1));
I am wondering why the following code doesn't compile:
struct S
{
template <typename... T>
S(T..., int);
};
S c{0, 0};
This code fails to compile with both clang and GCC 4.8. Here is the error with clang:
test.cpp:7:3: error: no matching constructor for initialization of 'S'
S c{0, 0};
^~~~~~~
test.cpp:4:5: note: candidate constructor not viable: requires 1 argument, but 2 were provided
S(T..., int);
^
It seems to me that this should work, and T should be deduced to be a pack of length 1.
If the standards forbids doing things like this, does anyone know why?
Because when a function parameter pack is not the last parameter, then the template parameter pack cannot be deduced from it and it will be ignored by template argument deduction.
So the two arguments 0, 0 are compared against , int, yielding a mismatch.
Deduction rules like this need to cover many special cases (like what happens when two parameter packs appear next to each other). Since parameter packs are a new feature in C++11, the authors of the respective proposal drafted the rules conservatively.
Note that a trailing template parameter pack will be empty if it is not otherwise deduced. So when you call the constructor with one argument, things will work (notice the difference of template parameter pack and function parameter pack here. The former is trailing, the latter is not).
So, there should be a workaround. Something along these lines:
namespace v1 {
// Extract the last type in a parameter pack.
// 0, the empty pack has no last type (only called if 1 and 2+ don't match)
template<typename... Ts>
struct last_type {};
// 2+ in pack, recurse:
template<typename T0, typename T1, typename... Ts>
struct last_type<T0, T1, Ts...>:last_type<T1, Ts...>{};
// Length 1, last type is only type:
template<typename T0>
struct last_type<T0> {
typedef T0 type;
};
}
namespace v2 {
template<class T> struct tag_t{using type=T;};
template<class T> using type_t = typename T::type;
template<class...Ts>
using last = type_t< std::tuple_element_t< sizeof...(Ts)-1, std::tuple<tag_t<Ts>...> > >;
template<class...Ts>
struct last_type {
using type=last<Ts...>;
};
}
template<class...Ts>
using last_type=v2::late_type<Ts...>; // or v1
struct S
{
// We accept any number of arguments
// So long as the type of the last argument is an int
// probably needs some std::decay to work right (ie, to implicitly work out that
// the last argument is an int, and not a const int& or whatever)
template <typename... T, typename=typename std::enable_if<std::is_same<int, typename last_type<T...>::type>>::type>
S(T...);
};
where we check that the last type of a parameter pack is an int, or that we where only passed an int.
I am actually a little interested in the same thing (wanting to specialize templated parameter packs based on the final arguments).
I believe there may be a path forward by combining tuple reversal (std::make_tuple, back-port std::apply for C++14, etc):
How to reverse the order of arguments of a variadic template function?
Will get back on here if it is successful.
Related posts:
Parameters after parameter pack in function
Parameter with non-deduced type after parameter pack
EDIT:
Yup, figured it out after a bit; not perfect, as there are extra copies flying around, but it's a start.
If you know a simpler way than what I list below, please don't hesitate to post!
TL;DR
Can do stuff like this:
auto my_func_callable = [] (auto&& ... args) {
return my_func(std::forward<decltype(args)>(args)...);
};
auto my_func_reversed =
stdcustom::make_callable_reversed(my_func_callable);
And then implement this pseduo code:
template<typename ... Args>
void my_func(Args&& ... args, const my_special_types& x);
By doing something like:
template<typename... Args>
void my_func(Args&& ... args)
-> call my_func_reversed(args...)
template<typename... RevArgs>
void my_func_reversed(const my_special_types& x, RevArgs&&... revargs)
-> do separate things with revargs and my_special_types
-> sub_func_reversed(revargs...)
Using the above utilities.
Has some (a lot of) drawbacks. Will list them below.
Scope
This is for users of C++14 (maybe C++11), who want to borrow from the future (C++17).
Step 1: Reverse arguments
There are a few different ways to do this. I've listed out some alternatives in this example:
tuple.cc - Playground for two alternatives (credits in the source code):
Use foldable expressions and manipulate the index passed via std::apply_impl (credit: Orient).
Use recursive templates to construct a reversed index_sequence (credit: Xeo)
tuple.output.txt - Example output
This prints out the reversed_index_sequence template from Xeo's example. I needed this for debugging.
>>> name_trait<std::make_index_sequence<5>>::name()
std::index_sequence<0, 1, 2, 3, 4>
>>> name_trait<make_reversed_index_sequence<5>>::name()
std::index_sequence<4, 3, 2, 1, 0>
I chose Alternative 1, as it's easier for me to digest.
I then tried to formalize it right quick:
tuple_future.h - Borrowing from the future (namespace stdfuture), and making an extension (namespace stdcustom)
tuple_future_main.cc - Simple, advanced, and useful (see below) examples using the above
tuple_future_main.output.txt - Example output
Definition Snippets (adaptation of C++17 possible implementation of std::apply on cppreference.com):
namespace detail {
template <class F, class Tuple, std::size_t... I>
constexpr decltype(auto) apply_reversed_impl(F &&f,
Tuple &&t, std::index_sequence<I...>)
{
// #ref https://stackoverflow.com/a/31044718/7829525
// Credit: Orient
constexpr std::size_t back_index = sizeof...(I) - 1;
return f(std::get<back_index - I>(std::forward<Tuple>(t))...);
}
} // namespace detail
template <class F, class Tuple>
constexpr decltype(auto) apply_reversed(F &&f, Tuple &&t)
{
// Pass sequence by value to permit template inference
// to parse indices as parameter pack
return detail::apply_reversed_impl(
std::forward<F>(f), std::forward<Tuple>(t),
std::make_index_sequence<
std::tuple_size<std::decay_t<Tuple>>::value>{});
}
Usage Snippets: (from tuple_future_main.output.txt, copied from above)
auto my_func_callable = [] (auto&& ... args) {
return my_func(std::forward<decltype(args)>(args)...);
};
auto my_func_reversed =
stdcustom::make_callable_reversed(my_func_callable);
Step 2: Buckle your shoe (with reversed parameter packs)
First, establish the patterns for the final arguments that you wish to use. You will have to explicitly enumerate these, as you can only have one parameter pack.
(Taken from tuple_future_main.cc):
Example Scenario:
We like to add things to containers with a name, something of the form:
add_item(const Item& item, const string& name, Container& c)
We can also construct an Item with a [awfully large] number of overloads, and
we have convenenience overloads:
add_item(${ITEM_CTOR_ARGS}, const string& name, Container& c)
To do so, we can declare the following:
void add_item_direct(const Item& item, const string& name, Container& c)
Item create_item(Args&&... args)
And then define our generic interfaces:
template<typename... Args>
void add_item(Args&&... args) {
...
auto reversed = stdcustom::make_callable_reversed(callable);
reversed(std::forward<Args>(args)...);
}
template<typename ... RevArgs>
void add_item_reversed(Container& c, const string& name, RevArgs&&... revargs)
{
...
static auto ctor = VARIADIC_CALLABLE(create_item,);
...
auto item = ctor_reversed(std::forward<RevArgs>(revargs)...);
add_item_direct(item, name, c);
}
Now we can do stuff like: (taken from tuple_future_main.output.txt)
>>> (add_item(Item("attribute", 12), "bob", c));
>>> (add_item("attribute", 12, "bob", c));
>>> (add_item(Item(2, 2.5, "twelve"), "george", c));
>>> (add_item(2, 2.5, "twelve", "george", c));
>>> (add_item(Item(2, 15.), "again", c));
>>> (add_item(2, 15., "again", c));
>>> c
bob - ctor3: ctor3: ctor1: attribute (12, 10)
bob - ctor3: ctor1: attribute (12, 10)
george - ctor3: ctor3: ctor2: 2, 2.5 (twelve)
george - ctor3: ctor2: 2, 2.5 (twelve)
again - ctor3: ctor3: ctor2: 2, 15 ()
again - ctor3: ctor2: 2, 15 ()
Note the extra copy constructors... :(
Drawbacks
Ugly as hell
May not be useful
It could be easier to just refactor your interfaces
However, this could be used as a stop-gap to transition to a more generalized interface.
Possibly fewer lines to delete.
Especially if it plugs your development process with template explosions
Can't nail down where the extra copies are coming from.
It may be due to judicious usage of variadic lambdas
You have to carefully craft your base functionality
You shouldn't try to extend an existing function.
Parameter packs will be greedy in how they match to functions
You either need to explicitly spell out each overload you want, or bow down and let the variadic parameter pack dispatch to your desired functionality
If you find an elegant way around this, please let me know.
Template errors are shitty.
Granted, not too shitty. But it's hard to infer that you missed an available overload.
Wraps a lot of simple functionality in lambdas
You may be able to use make_reversed_index_sequence and directly dispatch to the function (mentioned in other SO posts). But that's painful to repeat.
Todo
Get rid of extra copies
Minimize the need for all the lambdas
Not necessary if you have a Callable
Try to combat parameter pack greediness
Is there a generalized std::enable_if matching that matches to both lvalue- and rvalue-references, and possibly handle forwarding compatible implicit copy constructors?
template<typename ... Args>
void my_func(Args&& ... args) // Greedy
void my_func(magical_ref_match<string>::type, ...)
// If this could somehow automatically snatch `const T&` and `T&&` from the parameter pack...
// And if it can be used flexible with multiple arguments, combinatorically
Hopes
Maybe C++17 will support non-final parameter pack arguments, such that all of this can be discarded... fingers crossed
From the working draft of the standard N3376 § 14.1 is a probable section to read about this.
Below is § 14.1.11
If a template-parameter of a class template or alias template has a
default template-argument, each subsequent template-parameter shall
either have a default template-argument supplied or be a template
parameter pack. If a template-parameter of a primary class template or
alias template is a template parameter pack, it shall be the last
template-parameter. A template parameter pack of a function template
shall not be followed by another template parameter unless that
template parameter can be deduced from the parameter-type-list of the
function template or has a default argument.
I am wondering why the following code doesn't compile:
struct S
{
template <typename... T>
S(T..., int);
};
S c{0, 0};
This code fails to compile with both clang and GCC 4.8. Here is the error with clang:
test.cpp:7:3: error: no matching constructor for initialization of 'S'
S c{0, 0};
^~~~~~~
test.cpp:4:5: note: candidate constructor not viable: requires 1 argument, but 2 were provided
S(T..., int);
^
It seems to me that this should work, and T should be deduced to be a pack of length 1.
If the standards forbids doing things like this, does anyone know why?
Because when a function parameter pack is not the last parameter, then the template parameter pack cannot be deduced from it and it will be ignored by template argument deduction.
So the two arguments 0, 0 are compared against , int, yielding a mismatch.
Deduction rules like this need to cover many special cases (like what happens when two parameter packs appear next to each other). Since parameter packs are a new feature in C++11, the authors of the respective proposal drafted the rules conservatively.
Note that a trailing template parameter pack will be empty if it is not otherwise deduced. So when you call the constructor with one argument, things will work (notice the difference of template parameter pack and function parameter pack here. The former is trailing, the latter is not).
So, there should be a workaround. Something along these lines:
namespace v1 {
// Extract the last type in a parameter pack.
// 0, the empty pack has no last type (only called if 1 and 2+ don't match)
template<typename... Ts>
struct last_type {};
// 2+ in pack, recurse:
template<typename T0, typename T1, typename... Ts>
struct last_type<T0, T1, Ts...>:last_type<T1, Ts...>{};
// Length 1, last type is only type:
template<typename T0>
struct last_type<T0> {
typedef T0 type;
};
}
namespace v2 {
template<class T> struct tag_t{using type=T;};
template<class T> using type_t = typename T::type;
template<class...Ts>
using last = type_t< std::tuple_element_t< sizeof...(Ts)-1, std::tuple<tag_t<Ts>...> > >;
template<class...Ts>
struct last_type {
using type=last<Ts...>;
};
}
template<class...Ts>
using last_type=v2::late_type<Ts...>; // or v1
struct S
{
// We accept any number of arguments
// So long as the type of the last argument is an int
// probably needs some std::decay to work right (ie, to implicitly work out that
// the last argument is an int, and not a const int& or whatever)
template <typename... T, typename=typename std::enable_if<std::is_same<int, typename last_type<T...>::type>>::type>
S(T...);
};
where we check that the last type of a parameter pack is an int, or that we where only passed an int.
I am actually a little interested in the same thing (wanting to specialize templated parameter packs based on the final arguments).
I believe there may be a path forward by combining tuple reversal (std::make_tuple, back-port std::apply for C++14, etc):
How to reverse the order of arguments of a variadic template function?
Will get back on here if it is successful.
Related posts:
Parameters after parameter pack in function
Parameter with non-deduced type after parameter pack
EDIT:
Yup, figured it out after a bit; not perfect, as there are extra copies flying around, but it's a start.
If you know a simpler way than what I list below, please don't hesitate to post!
TL;DR
Can do stuff like this:
auto my_func_callable = [] (auto&& ... args) {
return my_func(std::forward<decltype(args)>(args)...);
};
auto my_func_reversed =
stdcustom::make_callable_reversed(my_func_callable);
And then implement this pseduo code:
template<typename ... Args>
void my_func(Args&& ... args, const my_special_types& x);
By doing something like:
template<typename... Args>
void my_func(Args&& ... args)
-> call my_func_reversed(args...)
template<typename... RevArgs>
void my_func_reversed(const my_special_types& x, RevArgs&&... revargs)
-> do separate things with revargs and my_special_types
-> sub_func_reversed(revargs...)
Using the above utilities.
Has some (a lot of) drawbacks. Will list them below.
Scope
This is for users of C++14 (maybe C++11), who want to borrow from the future (C++17).
Step 1: Reverse arguments
There are a few different ways to do this. I've listed out some alternatives in this example:
tuple.cc - Playground for two alternatives (credits in the source code):
Use foldable expressions and manipulate the index passed via std::apply_impl (credit: Orient).
Use recursive templates to construct a reversed index_sequence (credit: Xeo)
tuple.output.txt - Example output
This prints out the reversed_index_sequence template from Xeo's example. I needed this for debugging.
>>> name_trait<std::make_index_sequence<5>>::name()
std::index_sequence<0, 1, 2, 3, 4>
>>> name_trait<make_reversed_index_sequence<5>>::name()
std::index_sequence<4, 3, 2, 1, 0>
I chose Alternative 1, as it's easier for me to digest.
I then tried to formalize it right quick:
tuple_future.h - Borrowing from the future (namespace stdfuture), and making an extension (namespace stdcustom)
tuple_future_main.cc - Simple, advanced, and useful (see below) examples using the above
tuple_future_main.output.txt - Example output
Definition Snippets (adaptation of C++17 possible implementation of std::apply on cppreference.com):
namespace detail {
template <class F, class Tuple, std::size_t... I>
constexpr decltype(auto) apply_reversed_impl(F &&f,
Tuple &&t, std::index_sequence<I...>)
{
// #ref https://stackoverflow.com/a/31044718/7829525
// Credit: Orient
constexpr std::size_t back_index = sizeof...(I) - 1;
return f(std::get<back_index - I>(std::forward<Tuple>(t))...);
}
} // namespace detail
template <class F, class Tuple>
constexpr decltype(auto) apply_reversed(F &&f, Tuple &&t)
{
// Pass sequence by value to permit template inference
// to parse indices as parameter pack
return detail::apply_reversed_impl(
std::forward<F>(f), std::forward<Tuple>(t),
std::make_index_sequence<
std::tuple_size<std::decay_t<Tuple>>::value>{});
}
Usage Snippets: (from tuple_future_main.output.txt, copied from above)
auto my_func_callable = [] (auto&& ... args) {
return my_func(std::forward<decltype(args)>(args)...);
};
auto my_func_reversed =
stdcustom::make_callable_reversed(my_func_callable);
Step 2: Buckle your shoe (with reversed parameter packs)
First, establish the patterns for the final arguments that you wish to use. You will have to explicitly enumerate these, as you can only have one parameter pack.
(Taken from tuple_future_main.cc):
Example Scenario:
We like to add things to containers with a name, something of the form:
add_item(const Item& item, const string& name, Container& c)
We can also construct an Item with a [awfully large] number of overloads, and
we have convenenience overloads:
add_item(${ITEM_CTOR_ARGS}, const string& name, Container& c)
To do so, we can declare the following:
void add_item_direct(const Item& item, const string& name, Container& c)
Item create_item(Args&&... args)
And then define our generic interfaces:
template<typename... Args>
void add_item(Args&&... args) {
...
auto reversed = stdcustom::make_callable_reversed(callable);
reversed(std::forward<Args>(args)...);
}
template<typename ... RevArgs>
void add_item_reversed(Container& c, const string& name, RevArgs&&... revargs)
{
...
static auto ctor = VARIADIC_CALLABLE(create_item,);
...
auto item = ctor_reversed(std::forward<RevArgs>(revargs)...);
add_item_direct(item, name, c);
}
Now we can do stuff like: (taken from tuple_future_main.output.txt)
>>> (add_item(Item("attribute", 12), "bob", c));
>>> (add_item("attribute", 12, "bob", c));
>>> (add_item(Item(2, 2.5, "twelve"), "george", c));
>>> (add_item(2, 2.5, "twelve", "george", c));
>>> (add_item(Item(2, 15.), "again", c));
>>> (add_item(2, 15., "again", c));
>>> c
bob - ctor3: ctor3: ctor1: attribute (12, 10)
bob - ctor3: ctor1: attribute (12, 10)
george - ctor3: ctor3: ctor2: 2, 2.5 (twelve)
george - ctor3: ctor2: 2, 2.5 (twelve)
again - ctor3: ctor3: ctor2: 2, 15 ()
again - ctor3: ctor2: 2, 15 ()
Note the extra copy constructors... :(
Drawbacks
Ugly as hell
May not be useful
It could be easier to just refactor your interfaces
However, this could be used as a stop-gap to transition to a more generalized interface.
Possibly fewer lines to delete.
Especially if it plugs your development process with template explosions
Can't nail down where the extra copies are coming from.
It may be due to judicious usage of variadic lambdas
You have to carefully craft your base functionality
You shouldn't try to extend an existing function.
Parameter packs will be greedy in how they match to functions
You either need to explicitly spell out each overload you want, or bow down and let the variadic parameter pack dispatch to your desired functionality
If you find an elegant way around this, please let me know.
Template errors are shitty.
Granted, not too shitty. But it's hard to infer that you missed an available overload.
Wraps a lot of simple functionality in lambdas
You may be able to use make_reversed_index_sequence and directly dispatch to the function (mentioned in other SO posts). But that's painful to repeat.
Todo
Get rid of extra copies
Minimize the need for all the lambdas
Not necessary if you have a Callable
Try to combat parameter pack greediness
Is there a generalized std::enable_if matching that matches to both lvalue- and rvalue-references, and possibly handle forwarding compatible implicit copy constructors?
template<typename ... Args>
void my_func(Args&& ... args) // Greedy
void my_func(magical_ref_match<string>::type, ...)
// If this could somehow automatically snatch `const T&` and `T&&` from the parameter pack...
// And if it can be used flexible with multiple arguments, combinatorically
Hopes
Maybe C++17 will support non-final parameter pack arguments, such that all of this can be discarded... fingers crossed
From the working draft of the standard N3376 § 14.1 is a probable section to read about this.
Below is § 14.1.11
If a template-parameter of a class template or alias template has a
default template-argument, each subsequent template-parameter shall
either have a default template-argument supplied or be a template
parameter pack. If a template-parameter of a primary class template or
alias template is a template parameter pack, it shall be the last
template-parameter. A template parameter pack of a function template
shall not be followed by another template parameter unless that
template parameter can be deduced from the parameter-type-list of the
function template or has a default argument.
This question already has answers here:
What is the meaning of "... ..." token? i.e. double ellipsis operator on parameter pack
(2 answers)
Closed 8 years ago.
While looking at this question I found myself in the cpp reference site where I noticed a strange and new to me syntax :
template<class Ret, class... Args>
struct is_function<Ret(Args......)volatile &&> : std::true_type {};
Yep, 6 dots ! Initially I thought this was a typo, but after checking the libstdc++ source again there it was eg at line 444 :
template<typename _Res, typename... _ArgTypes>
struct is_function<_Res(_ArgTypes......) volatile &&> : public true_type { };
Is this a valid syntax ? Dot dot dot, are used to pack and unpack parameter packs ? What do 6 dots do ?
Why does libstdc++ use ... ... in it's implementation of is_function? If we check out the cppreference section for std::is_function it gives a sample implementation and says for the first ... ... case:
// specialization for variadic functions such as std::printf
template<class Ret, class... Args>
struct is_function<Ret(Args......)> : std::true_type {};
so we need the second set of ... to match a variadic function like printf:
Comma optional as per 8.3.5 [dcl.fct]
|
v
Ret(Args... ...)
^ ^
| |
Match a function with a variable number of arguments
|
and the function is a variadic function
Note, we have functions like fprintf that two arguments before the variadic terms and we need to match those as well.
Indeed if we use that implementation and attempt to match printf without the ... ... specialization then it fails see it live.
This corner of the language is covered in this post C++11's six dots:
I was mucking around the other day and discovered this nice little oddity:
template <typename... Args>
void foo(Args......);
As it turns out, ...... can be totally valid C++11. This is what happens when backward compatibility mixes with new hotness.
// These are all equivalent.
template <typename... Args> void foo1(Args......);
template <typename... Args> void foo2(Args... ...);
template <typename... Args> void foo3(Args..., ...);
Hopefully the last one shows what is happening here. [...]
Why is this valild? We can see that , ... is synonymous with ... from the draft C++11 standard section 8.3.5 [dcl.fct] which has the following grammar:
parameter-declaration-clause:
parameter-declaration-listopt...opt
parameter-declaration-list , ...
and says:
[...] Where syntactically correct and where “...” is not part of
an abstract-declarator, “, ...” is synonymous with “...”. [...]
In this case, the two are for different purposes. The first is for parameter pack expansion and the second is for variable argument lists. That particular declaration is to handle functions which take some regular parameters plus a variable argument list.
The difference is between run-time and compile-time variability. A function which takes a variable number of arguments at run-time is special. It is a single function which can handle a variable number of arguments from the caller:
void f(int x,...) // equivalent to void f(int x ...)
{
// Do some run-time logic here to determine what to
// do with parameters after x.
}
This is distinct from the notion that we want to be able to have a template which uses a variety of functions with various parameters which are known at compile time. For example, we could define a function template which takes a pointer to a function and allow the number and types of the arguments to vary:
template <typename... Args>
void g(void (*function_ptr)(Args...))
{
// We can do logic here to call function_ptr with the proper
// number of arguments.
}
Given these functions:
void f1(int);
void f2(int,float);
You can call g with any of them:
g(f1); // fine
g(f2); // also fine
However
g(f); // error
The compiler wouldn't know what to use for the Args parameter pack in g.