On Friday, March 11, 2016 at 12:56:48 PM UTC+1, Paul wrote:
> Suppose I want to sort a vector in reverse order, using std::greater. I use decltype because I don't want to explicitly state the type but I want to select the type of the first element.
>
> The code snippet is below:
>
> std::vector<int> v{1};
> std::sort(v.begin(), v.end(), std::greater<decltype(v[0])>());
>
> Why does this fail to compile? If v[0] is replaced by 5, it does compile. My thinking is that decltype(v[0]) means same type as v[0] which means same type as 1 which is int and that therefore std::sort(v.begin(),v.end(), std::greater<int>()); should be executed.
decltype is kind of overloaded. IIRC there are three cases to consider:
(1) It gives you the *declared* type of some item.
(2) It gives you the return type of whatever function you called.
(3) It gives you the type and "lvalueness" of an arbitrary expression:
T& for lvalue expressions of type T,
T for rvalue expressions of type T
An extra set of parentheses surrounding decltype's argument basically
forces case 3.
Examples:
struct S { int m; }
int i = 42;
int& L = i;
int&& R = std::move(i);
const S s = { 99 };
std::vector<double> vec = { 3.14 };
// Examples for case 1: "declared type"
decltype(i) // int
decltype(L) // int&
decltype(R) // int&&
decltype(s.m) // int (S::m is not declared const)
// Examples for case 3: "type and lvalueness of expression"
decltype(+i) // int
decltype((i)) // int&
decltype((L)) // int&
decltype((R)) // int&
decltype((s.m)) // const int& (s is const, so is s.m)
decltype((std::move(i))) // int
// Examples for case 2: "return type of function"
decltype(std::move(i)) // int&&
decltype(std::rand()) // int
decltype(vec[0]) // double&
> Where am I going wrong? What should be done in practice if the type of v[0] is not simply int but a very complex type, and I want to avoid repeating the name of the type? I suppose typedef is the solution, but I thought decltype was used to solve this type of problem, too.
You could use
typename std::decay<decltype(v[0])>::type // C++11, or
std::decay_t<decltype(v[0])> // C++14
The type transformation trait std::decay basically gets rid of
references as well as const and volatile. std::decay_t is a templated
type alias since C++14 which makes this shorter. But if you're OK
with reling on C++14, you could just as well write
std::sort(v.begin(), v.end(), std::greater<>());
This creates a std::greater<void> which is specialized to have a
generic function call operator since C++14. :-)
Cheers!
sg