I have a templated function f
. I am passing it either a reference or a pointer to an object. That object is of struct S
. I would like to know the size of S::my_tuple
, which is a static member of that struct.
I can do that when I pass the object by reference with std::tuple_size<decltype(T::my_tuple)>::value
.
How can I do this for a pointer? This currently fails, since my_tuple
is not a member of S*
#include <tuple>
#include <type_traits>
#include <iostream>
struct S {
constexpr static auto my_tuple = std::make_tuple(1, 2, 3, 4);
};
template <typename T>
int f(const T object) {
// fails if T is a pointer
if (std::is_pointer<T>::value) {
// error
return std::tuple_size<decltype(T::my_tuple)>::value;
}
// works if T is a reference
return std::tuple_size<decltype(T::my_tuple)>::value;
}
int main() {
S my_struct;
std::cout << f(my_struct); // 4, correct size of properties
S* my_ptr = new S;
std::cout << f(my_ptr); // does not compile
}
EDIT:
Thanks for the support. Here is the solution
Just remove pointer with a type trait:
decltype(std::remove_pointer_t<std::decay_t<T>>::my_tuple)
In const T object
, T
is never a reference. std::decay_t<T>
is needed if you use, for example, a universal reference (T&& object
) instead.
Also note that all branches of your code should compile when you use if
. If you want to discard a branch based on std::is_pointer_v<T>
, take a look at constexpr if
:
if constexpr (std::is_pointer_v<T>) {
// ...
} else {
// this code can be invalid if T is a pointer
}
Something like
if (std::is_pointer_v<T>)
return std::tuple_size_v<decltype(std::remove_pointer_t<T>::my_tuple)>;
else
return std::tuple_size_v<decltype(T::my_tuple)>;
will not work in general.
In your particular example you can just always remove pointer, because if T
is not a pointer type, std::remove_pointer_t<T>
is T
itself:
template <typename T>
std::size_t f(T&&) {
using Tuple = decltype(std::remove_pointer_t<std::decay_t<T>>::my_tuple);
return std::tuple_size_v<Tuple>;
}