I am currently learning metaprograming in C++, and I'm trying to see whether an element of a tuple is a pointer. I tried this approach:
int a = 3, b = 4;
auto tup = std::make_tuple(&a, b);
std::cout << std::is_pointer<decltype(std::get<0>(tup))>::value; //prints 0
I thought this was strange, so I examined the type clang deduced (I'm using clang-10), which is
__tuple_element_t<0UL, tuple<int *, int>
And it looks like some internal type.
Why do I get this weird type and what would be the proper way to get the actual type of an element of the tuple? I have only a solution which uses an intermediate auto
variable, but is hardly optimal.
The decltype type specifier yields the type of a specified expression. The decltype type specifier, together with the auto keyword, is useful primarily to developers who write template libraries. Use auto and decltype to declare a template function whose return type depends on the types of its template arguments.
If the expression parameter is a call to a function or an overloaded operator function, decltype(expression) is the return type of the function. Parentheses around an overloaded operator are ignored. If the expression parameter is an rvalue, decltype(expression) is the type of expression.
decltype returnsIf what we pass to decltype is the name of a variable (e.g. decltype(x) above) or function or denotes a member of an object ( decltype x.i ), then the result is the type of whatever this refers to.
decltype is a compile time evaluation (like sizeof ), and so can only use the static type.
std::is_same
/std::is_same_v
can be very helpful in TMP and, when looking for types being equal to other types, it's invaluable when used in conjunction with static_assert
.
With the following code you can see that std::get
gives you a reference to the element of the tuple (as confirmed by cppreference's page on std::get
), in this case int*&
, where int*
is the type of the element. If you use it to initialize another variable you get a copy of it (so no more reference for elem
, just int*
), just like int x = r;
defines x
to be a copy of r
regardless of r
being a reference or not.
#include <type_traits>
#include <tuple>
int main() {
int a = 3, b = 4;
auto tup = std::make_tuple(&a, b);
auto elem = std::get<0>(tup);
static_assert(std::is_same_v<decltype(elem), int*>,"");
static_assert(std::is_same_v<decltype(std::get<0>(tup)), int*&>,"");
}
As regards your attempt, the fact that the second static_assert
above is passing, explains why std::is_pointer<decltype(std::get<0>(tup))>::value
prints false
/0
: that is a reference to int*
, not an int*
. On the other hand, the following does print true
/1
:
std::cout << std::is_pointer_v<std::remove_reference_t<decltype(std::get<0>(tup))>>;
See that I've used is_pointer_v
instead of is_pointer
and is_same_v
instead of is_same
? Those with _v
are helper metafunctions that give you the value
member of the non-_v
metafunctions. remove_reference_t
works similarly with respect to remove_reference
, but giving the type
member.
As Enrico explained, the type you were getting was a reference. In addition to his answer, I'd say you can get the actual type of the element of the tuple more easily with:
std::tuple_element_t<0, decltype(tup)>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With