Why does expanding a package inside an unvalued operand result in the last element?

I can do this internally decltype()

:

auto g() -> decltype(1, "", true, new int);

      

But not this:

template <class... Args>
auto g(Args&&... args) -> decltype(args...);

      

This fails because the package extension appears internally decltype()

, but I thought the package extension would result in a comma separated list of arguments. So the return type g(a, b, c)

will be decltype(c)

because of the way the comma operator works (it returns the last element). It works when you expand inside a function parameter list, template parameter list, initializer list, etc. But why is this not so here?

+3


source to share


2 answers


Option packs only expand under certain circumstances. You can find them in the standard by looking for "package extension". For example,

A function parameter package is an extension of the package (14.5.3).



(8.3.5 / 14).

Unless it is explicitly stated somewhere that package expansion occurs in a particular context, it does not happen and is usually forbidden by the grammar (i.e. syntactically incorrect). For example, it decltype

requires an expression as its operand. 1, "", true, new int

is indeed an expression ( ,

is the comma operator), but is args...

not an expression. However args...

, it is a list of expressions, so it can be used, for example, in a function call.

+5


source


The comma operator does not match the comma expression separator.

The comma operator takes two expressions, evaluates the left side, discards, evaluates the right side, and returns the result.

The expression separator is used when you have a list of expressions, such as a function call or a list of initializers.

decltype(a,b,c)

decltype(

expression )

, not decltype(

list-expression )

. This means that ,

in your decltype

is a service comma.

In general, the extension ...

only works when the grammar allows a list of expressions. The generated ,

is the expression separator, not the comma operator.

I don't know how you can emulate operator behavior ,

including execution order using ...

. If you don't care what order they are evaluated in, you can do:



template<class T, class... Ts>
struct last_type_helper{using type=T;};
template<class T0, class T1, class... Ts>
struct last_type_helper<T0, T1, Ts...>:last_type_helper<T1, Ts...>{}
template<class... Ts>
using last_type=typename last_type_helper<Ts...>::type;

template<class T0>
T0&& last_expression( T0&& t0 ) { return std::forward<T0>(t0); }
template<class T0, class...Ts>
auto last_expression( T0&& t0, Ts&&...ts )->last_type<T0, Ts...>&& {
  return last_expression( std::forward<Ts>(ts)... );
}

      

then

template<class...Args>
auto g(Args&&...args) -> decltype(last_expression(args...));

      

works like

template<class...Args>
auto g(Args&&...args) -> last_type<Args...>;

      

which puts the basket after the horse, no?

+4


source







All Articles