3

I can do this inside decltype():

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

But not this:

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

It fails because a pack expansion appears inside decltype() but I thought a pack expansion would result in a comma separated list of arguments. So the return type of g(a, b, c) would be decltype(c) because of how 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 here is this not the case?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
template boy
  • 10,230
  • 8
  • 61
  • 97
  • 4
    I'm reasonably sure that this is not how it works. Pack expansion doesn't ever give you an expression with a comma separator. It gives you different lists depending on context `"Depending on where the expansion takes place, the resulting comma-separated list is a different kind of list: function parameter list, member initializer list, attribute list, etc."`. But never does it give you an expression with the comma-operator. It's not just text-replacement like a macro. – PeterT Oct 31 '14 at 19:22

2 Answers2

5

Parameter packs are only expanded under certain circumstances. You can find them in the standard by searching for "pack expansion". For example,

A function parameter pack is a pack expansion (14.5.3).

(8.3.5/14).

Unless it is explicitly specified somewhere that a pack expansion occurs in a particular context, it doesn't happen, and is usually forbidden by the grammar (i.e., syntactically incorrect). For example, decltype requires an expression as its operand. 1, "", true, new int is indeed an expression (the , is the comma operator) but args... is not an expression. However, args... is an expression-list, so it may be used, e.g., in a function call.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
4

The comma operator is not the same as the comma expression separator.

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

The expression separator is used when you have a list of expressions, like a function call or initializer list.

decltype(a,b,c) is decltype( expression ), not decltype( expression-list ). Which means that the , in your decltype is operator comma.

In general, ... expansion only works when the grammar allows a list of expressions. The , "generated" is the expression separator, not the comma operator.

I am not aware of a way you can emulate the behavior of the , operator, 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, as does

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

which puts the cart after the horse, no?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524