Motivation:
Almost for fun, I am trying to write a function overload that can tell apart whether the argument is a fixed-size array or a pointer.
I like using tag dispatching:
void foo(char const*, std::true_type /*is_pointer*/) {
std::cout << "is pointer\n";
}
template<class T, size_t N>
void foo( T(&)[N], std::false_type /*is_pointer*/) {
std::cout << "is array\n";
}
template<class X>
void foo( X&& x ) {
foo( std::forward<X>(x), std::is_pointer<std::remove_reference_t<X>>{} );
}
live example
Here is a simple solution that exploits the fact that in C the value of an array is equal to its address while this is generally not true for a pointer.
#include <iostream>
template <typename P>
bool is_array(const P & p) {
return &p == reinterpret_cast<const P*>(p);
}
int main() {
int a[] = {1,2,3};
int * p = a;
std::cout << "a is " << (is_array(a) ? "array" : "pointer") << "\n";
std::cout << "p is " << (is_array(p) ? "array" : "pointer") << "\n";
std::cout << "\"hello\" is " << (is_array("hello") ? "array" : "pointer");
}
Note that while a pointer normally points to a location different from itself, this is not necessarily guaranteed; indeed you can easily fool the code by doing something like this:
//weird nasty pointer that looks like an array:
int * z = reinterpret_cast<int*>(&z);
However, since you are coding for fun, this can be a fun, basic, first approach.
You may use the following:
namespace detail
{
template <typename T> struct helper;
template <typename T> struct helper<T*> { void operator() () const {std::cout << "pointer\n";} };
template <typename T, std::size_t N> struct helper<T[N]> { void operator() ()const {std::cout << "array\n";} };
}
template <typename T>
void f(const T& )
{
detail::helper<T>{}();
}
Live example
In my opinion it is as simple as this:
#include<iostream>
using namespace std;
template<typename T>
void foo(T const* t)
{
cout << "pointer" << endl;
}
template<typename T, size_t n>
void foo(T(&)[n])
{
cout << "array" << endl;
}
int main() {
int a[5] = {0};
int *p = a;
foo(a);
foo(p);
}
I don't get all the complication with std::enable_if
and std::true_type
and std::is_pointer
and magic. If there's anything wrong with my approach please tell me.
This seems to work for me
#include <iostream>
template<typename T>
std::enable_if_t<std::is_pointer<T>::value>
foo(T) { std::cout << "pointer\n"; }
template<typename T, std::size_t sz>
void foo(T(&)[sz]) { std::cout << "array\n"; }
int main()
{
char const* c;
foo(c);
foo("hello");
}
Bonus std::experimental::type_traits
:
using std::experimental::is_pointer_v;
std::enable_if_t<is_pointer_v<T>>
Your comment made me try something even simpler
template<typename T> void foo(T) { std::cout << "pointer\n"; }
template<typename T, unsigned sz> void foo(T(&)[sz]) { std::cout << "array\n"; }
Of course the problem here is that foo
is now callable for any type, depends on how lax you want your parameter checking to be.
One other way is to (ab)use rvalue references
void foo(char const*&) { std::cout << "pointer\n"; }
void foo(char const*&&) { std::cout << "array\n"; }
Obviously it's not foolproof.
I too was wondering how to get around the compiler complaining about ambiguous overloads and fell across this.
This C++11 solution is similar in form to the dispatching answer but makes use of the variadic SFINAE trick instead (the compiler attempts the array version first). The "decltype" part allows different return types. If the required return type is fixed (e.g. "void" as per the OP) then it is not required.
#include <functional>
#include <iostream>
using std::cout;
using std::endl;
template <typename T> T _thing1(T* x, ...) { cout << "Pointer, x=" << x << endl; return x[0]; }
template <typename T, unsigned N> T _thing1(T (&x)[N], int) { cout << "Array, x=" << x << ", N=" << N << endl; return x[0]; }
template <typename T> auto thing1(T&& x) -> decltype(_thing1(std::forward<T>(x), 0)) { _thing1(std::forward<T>(x), 0); }
int main(int argc, char** argv)
{
const int x0[20] = {101,2,3};
cout << "return=" << thing1(x0) << endl;
int x1[10] = {22};
cout << "return=" << thing1(x1) << endl;
float x2 = 3.141;
cout << "return=" << thing1(&x2) << endl;
const float x3 = 55.1;
cout << "return=" << thing1(&x3) << endl;
}
Example output is:
$ g++ -std=c++11 array_vs_ptr.cpp -o array_vs_ptr && ./array_vs_ptr
Array, x=0x22ca90, N=20
return=101
Array, x=0x22ca60, N=10
return=22
Pointer, x=0x22caec
return=3.141
Pointer, x=0x22cae8
return=55.1