How is type deduced from auto return type? [duplicate]
Asked Answered
T

2

8

This answer has a code snippet like this :

template<class T, class F>
auto f(std::vector<T> v, F fun)
    -> decltype( bool( fun(v[0] ) ), void() )
{
  // ...
}

It really compiles and work (at least on Ideone).

So, how is the type deduced in this case?

Is next line really allowed by c++11 standard?

decltype( bool( fun(v[0] ) ), void() )

I took a quick look, and it doesn't look valid. Is ideone wrong in this case?


All examples in the c++11 standard are such that they all got only one type in the decltype :

struct A {
  char g();
  template<class T> auto f(T t) -> decltype(t + g())
  { return t + g(); }
};

another example :

void f3() {
  float x, &r = x;
  [=] {
  decltype(x) y1;
  decltype((x)) y2 = y1;
  decltype(r) r1 = y1;
  decltype((r)) r2 = y2;
};

and another

const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = i;
decltype(i) x2;
decltype(a->x) x3;
decltype((a->x)) x4 = x3;

They all got only one parameter in decltype. How come the top code take two parameters (separated by a comma)?


I created another example (which fails to compile) :

#include <vector>
#include <iostream>

template<class T, class F>
auto f(std::vector<T> v, F fun) -> decltype(bool(fun(v[0])), void())
{
  // ...
  (void)v;(void)fun;

  return fun(v.size());
}

void ops(int)
{
}

int main(){
  std::vector<int> v;
  f(v, [](int){ return true; });
  f(v,ops);
}

Even if the line f(v,ops); is removed, the return type of the f template function is evaluated to void.

Tripod answered 2/8, 2012 at 10:29 Comment(2)
Why do you think it doesn't look valid? I don't really know what to put in an answer that will help, besides "It is valid".Conferee
@R.MartinhoFernandes Hope it's more clear after the edit. The question is what happens here :decltype( bool( fun(v[0] ) ), void() )Humility
E
14

decltype( bool( fun(v[0] ) ), void() ) uses the comma operator.

Breaking it down,

bool( fun(v[0] ) ), void()

is composed of two expressions; the first

bool( fun(v[0] ) )

is evaluated1 and discarded, giving the overall expression the value

void()

which is a value2 of type void.

decltype then yields the type of the expression, which as above is void.

The reason to use the comma operator here is to ensure that the whole expression is only valid if the first subexpression is valid; this is because it is being used in SFINAE to exclude it from substitution consideration if the first subexpression is invalid.

This works because although decltype looks syntactically like a function, it is actually a language construct that (like sizeof) is defined to take a single argument. It might be clearer to parenthesise the comma-operator argument:

decltype( ( bool( fun(v[0] ) ), void() ) )

Notes

  1. The expression bool( fun(v[0] ) ) is not actually evaluated, because we're in a non-evaluated context (decltype, similar to sizeof). What matters here is that it would be evaluated if the expression as a whole was evaluated, so that if the subexpression is invalid then the whole expression is invalid.
  2. void() isn't really a value, but it behaves like a value in the context of the comma operator and decltype.
Egidio answered 2/8, 2012 at 10:38 Comment(9)
Is the first operand of the comma really evaluated?Phosphoresce
@Kerrek Well, in this case, nothing is evaluated.Conferee
@Kerrek: It would be, if the whole expression wasn't inside a non-evaluated context (decltype in this case). ;)Ethical
Does void() really mean void? In template declarations it's an anonymous pointer to a function returning void, isn't it?Prevot
@jrok: Where a type is required, (like std::function<void()>), it's a function type. In a function parameter, it's still a function type, but is transformed, per language requirement, to be equivalent to void (*)(), aka a function pointer type. Where an expression is required, it's a "value" of type void. Which can be abused, like here. ;)Ethical
@Michael: Why wouldn't it? Evaluating means calling functions, operators, ctors, whatever else, and none of that is needed to have the typesystem shout at you because you provided non-matching types. Values are completely non-interesting here, only types matter, and for those, you don't need to evaluate anything.Ethical
So, the return type is always void?Humility
@BЈовић: The return type of f will always be void, yes.Ethical
I call the "evaluations" that decltype and sizeof perform "e-type-uations" :)Postdiluvian
E
7

decltype yields the type of the expression between the parenthesis, without actually evaluating it (keep that in mind for the next parts).

The , operator evaluates the left argument / expression, throws the result away, evaluates the right argument, and yields that result. As such, the return type becomes void.

For the bool(fun(v[0])) part, it's rather easy. bool(f(...)) constructs a bool temporary from the result of calling f. If the return type of f isn't convertible to bool, this will trigger an error, which will lead to SFINAE thanks to being inside decltype (this is called "Expression SFINAE").

f(v[0]) will pass the return value of v[0] to f, which is of type T&. If f doesn't have a parameter that T& is convertible to, or takes more / less parameters, this will trigger an error, and again, will lead to SFINAE for the same reason as above.

(The same would happen if std::vector wouldn't support operator[].)

Ethical answered 2/8, 2012 at 10:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.