Understanding decltype
Table of contents
where we usually use decltype.
it is a way to express the type of the objects, let's say you don't know the return type of the func1 and you want to use the same type of entry into another func that takes the same type, the first approach that came to your mind is Auto, but remember the last time about the template deduced types rules are applied in case of auto. what about decltype.
const int x=0; // decltype(x) is const int
bool f(const widget&w); // decltype(f) bool(const widget&)
// decltype(w) const widegt&
// decltype(f(w)) is bool
vector<int>v. //decltype(v) is vector<int>
if(v[0]==0) // decltype(v[0]) is int&
int i=2; // now i is int
decltype(i)j=i*2; // now the j is int&
std::cout << "i and j are the same type? " << std::boolalpha<< std::is_same_v<decltype(i), decltype(j)> << '\n';
// yes they are the same
It can be used to return the same type of parameter's type for a function. for example, we would like a function to take a container described by [] and index, the return of the function should match the index operation. operator [] return T& this is always the case for deque or the vectors in general, except for vector<bool> it doesn't return bool& instead a new object. so basically the type returned by a container is entirely dependent on the container. decltype make sit easy to express. a first real example of the usage of the decltype.
template<typename container,typename index>
auto auth(container& c, index i)->decltype(i)
{
authenticateUser();
return c[i];
}
the auto here has nothing to do with the deduction of the input but rather with the return type specified by -> in the function. the main advantage is that the function's param can be used in the specifications of the return type. but here is a thing you can't use the function until you specify the input itself, as the return of the function depends on c and i. this is what every programmer looking for. this is the so-called trailing return type. bu t since the C++ 14 came to place we can omit the trailing return type -> and leave the leading auto to deduce the return of the function.
why do we need decltype in that case, well the problem with templates is deduction, although it sounds natural sometimes it behaves weirdly, and the references-ness is ignored in case of template usage. for example
std:: deque<int>d;
....
auth(d,5)=10; // the auth return d[5] then assgin 10 to it. nooo error!!!!
here d[5] returns a reference to a value AKA int&, but the auto deduction works and strips off the reference, thus yielding a return type of int. that int being the return of the function is an rvalue int. that's why the code will not compile. so to make it works we will use decltype that keeps the references of the objects.
template<typename container,typename index>
decltype(auto) auth(container& c, index i)
{
authenticateUser();
return c[i];
}
now the above function will truly return what c[i] returns, in many cases where c[i] return T& also auth will return T& and where c[i] return an object-like the case of bool, also auth return object too. the usage of decltype(auto) is not limited to function it is also convenient for declaring vars.
int x;
const int& rx=x;
auto xx=rx; // auto type deduction; now it is int
decltype(auto) x2=rx // now x2 is const int&;
even after the modification to the function auth but still don't accept the rvalue or able to change the rvalue as the rvalue is going to be destroyed by the end of the line and the reference would dangle at the end of the statment . as the rvalue can't be combined with lvalue in the function param unless the lvalue is reference-to-const which is not our case. so here were universal references works.
template<typename container,typename index>
decltype(auto) auth(container&& c, index i)
passing the index of unknown type to a container raises the high risk of extra copying and hitting the performance of the container. so we typically uses
template<typename container,typename index>
decltype(auto) auth(container& c, index i)
{
authenticateUser();
return std::forward<container>(c)[i];
}
it is not the same in c++11 you got to use the trailing tail. decltype can change the type that decltype reports for ex:
int x=0; //decltype(x) yeilds int
// decltype((x)) yeilds int&
// c++ defines the (x) as an lvalue
Things to Remember
decltype almost always yields the type of a variable or expression without any modifications.
For lvalue expressions of type T other than names, decltype always reports a type of T&.
C++14 supports decltype(auto), which, like auto, deduces a type from its initializer, but it performs the type deduction using the decltype rules.
Subscribe to my newsletter
Read articles from Amr Shams directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Amr Shams
Amr Shams
I am a problem solving, in love with C++, python, and Operating systems.