27

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.

double const  d[] = {1.,2.,3.};
double a;
double const* p = &a;
f(d); // call array version
f(p); // call pointer version

I find this particularly difficult because of the well known fact that arrays decay to pointer sooner than later. A naive approach would be to write

void f(double const* c){...}
template<size_t N> void f(double const(&a)[N]){...}

Unfortunately this doesn't work. Because in the best case the compiler determines the array call f(d) above to be ambiguous.

Partial solution:

I tried many things and the closest I could get was the following concrete code. Note also that, in this example code I use char instead of double, but it is very similar at the end.

First, I have to use SFINAE to disable conversions (from array ref to ptr) in the pointer version of the function. Second I had to overload for all possible arrays sizes (manually).

[compilable code]

#include<type_traits> // for enable_if (use boost enable_if in C++98)
#include<iostream>
template<class Char, typename = typename std::enable_if<std::is_same<Char, char>::value>::type>
void f(Char const* dptr){std::cout << "ptr" << std::endl;} // preferred it seems

void f(char const (&darr)[0] ){std::cout << "const arr" << std::endl;}
void f(char const (&darr)[1] ){std::cout << "const arr" << std::endl;}
void f(char const (&darr)[2] ){std::cout << "const arr" << std::endl;}
void f(char const (&darr)[3] ){std::cout << "const arr" << std::endl;}
void f(char const (&darr)[4] ){std::cout << "const arr" << std::endl;}
void f(char const (&darr)[5] ){std::cout << "const arr" << std::endl;}
void f(char const (&darr)[6] ){std::cout << "const arr" << std::endl;} // this is the one called in this particular example
// ad infinitum ...

int main(){     
    f("hello"); // print ptr, ok because this is the fixed size array
    f(std::string("hello").c_str()); // print arr, ok because `c_str()` is a pointer
}

This works, but the problem is that I have to repeat the function for all possible values of N and using template<size_t N> gets me back to square zero, because with the template parameter the two calls get back to equal footing. In other words, template<size_t N> void f(char const(&a)[N]){std::cout << "const arr" << std::endl;} doesn't help.

Is there any way to generalize the second overload without falling back to an ambiguous call? or is there some other approach?

A C++ or C++1XYZ answer is also welcome.

Two details: 1) I used clang for the experiments above, 2) the actual f will end up being an operator<<, I think know if that will matter for the solution.


Summary of solutions (based on other people's below) and adapted to a the concrete type char of the example. Both seem to depend on making the char const* pointer less obvious for the compiler:

  1. One weird (portable?), (from the comment of @dyp.) Adding a reference qualifier to the pointer version:
    template<class Char, typename = typename std::enable_if<std::is_same<Char, char>::value>::type>
    void f(Char const* const& dptr){std::cout << "ptr" << std::endl;}
    
    template<size_t N>
    void f(char const (&darr)[N] ){std::cout << "const arr" << std::endl;}
  1. One elegant (special case from @user657267)
    template<class CharConstPtr, typename = typename std::enable_if<std::is_same<CharConstPtr, char const*>::value>::type>
    void f(CharConstPtr dptr){std::cout << "ptr" << std::endl;}
    
    template<size_t N>
    void f(char const (&darr)[N] ){std::cout << "const arr" << std::endl;}
alfC
  • 14,261
  • 4
  • 67
  • 118
  • How about using `std::array` instead of raw arrays? – user657267 Jan 28 '15 at 00:54
  • @user657267, thanks, I just want a solution not involving changing the code in the main function. The reason, if you want to know is that I want to tell hard coded text (`"hello"`) from text resulting from a pointer. – alfC Jan 28 '15 at 00:56
  • 3
    There's a weird work-around: http://coliru.stacked-crooked.com/a/37214a2859f7e78d A more obvious solution is to use tag dispatch. – dyp Jan 28 '15 at 01:00
  • Can you overload based on constness instead? – Thomas Jan 28 '15 at 01:01
  • @Thomas, I tried that in the `main` code both are `const` so I can't exploit that, I think – alfC Jan 28 '15 at 01:01
  • @dyp, thanks your worked too, although it is really weird why. – alfC Jan 28 '15 at 01:22
  • Taking the pointer by const reference blocks array-to-pointer decay during template argument deduction. – T.C. Jan 28 '15 at 01:49
  • @T.C. , ...only if there is a template deduction also involved, simply writing `void f(char const* const& dptr){std::cout << "ptr" << std::endl;}` doesn't solve the issue. In my `clang++` at least. – alfC Jan 28 '15 at 06:30
  • @alfC I did say "during template argument deduction" :) – T.C. Jan 28 '15 at 10:39
  • @T.C., sorry it was late. – alfC Jan 28 '15 at 19:08
  • One problem with #2, that #1 doesn't have is that it's "function not found" for a non-const pointer. Which means you'd eventually run into writing something like `void g(char * p) { f(const_cast(p)); }` – Glen Knowles Dec 15 '16 at 00:01

6 Answers6

31

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.

user657267
  • 20,568
  • 5
  • 58
  • 77
  • Great, you got it, as soon as one removes the pointer explicitly from the argument type the decay doesn't happen. I posted in the question the solution in the style of my original code (for `char` types in particular.) – alfC Jan 28 '15 at 01:10
  • Comment to your edit: actually that is not a problem, see my summary of solutions above. (I put the summary to give the concrete case of a `char`-only based overload) – alfC Jan 28 '15 at 01:20
  • N.B. things in `std::experimental` are not part of "c++1z" or any other draft/future standard, they're in a separate namespace for a reason. – Jonathan Wakely Jan 28 '15 at 10:12
  • @JonathanWakely Looking at [N3932](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3932.htm) I'm not sure the `_v` types should be in `experimental`, aren't they straight up additions to the library? – user657267 Jan 28 '15 at 13:50
  • 1
    @user657267 that's what the proposal said, but that's not what got approved by the committee. They didn't get added to the C++ working paper, they went in the [Library Fundamentals TS](https://rawgit.com/cplusplus/fundamentals-ts/v1/fundamentals-ts.html) and so are in namespace `std::experimental` – Jonathan Wakely Jan 28 '15 at 14:11
9

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

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Thanks, your solution is probably the most "formal" one. Two comments: 1) the member function can be `static` (e.g. called `doit`) and 2) the specialization can be for a concrete type (in my case `char`), as in `struct helper...` and `...size_t N> struct helper...` – alfC Jan 28 '15 at 01:25
  • 1
    This seems prettier. It is more like pattern matching instead of `if` statement as in `std::enable_if`. – Siyuan Ren Jan 28 '15 at 07:20
7

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

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 1
    thanks, I guess the template `T` in the second function is not needed if one wants this only for char. `template void foo(char const(&)[N], std::false_type){...}` – alfC Jan 28 '15 at 06:37
6

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.

Community
  • 1
  • 1
DarioP
  • 5,377
  • 1
  • 33
  • 52
  • The goal is to call a different overloaded function (meaning it must be resolved at compile-time) ... not sure if your `is_array` counts as that, even if you declare it `constexpr` – M.M Jan 28 '15 at 04:19
  • @MattMcNabb I agree with you, I was not sure if the goal was to create overloads or just tell an array from a pointer... – DarioP Jan 28 '15 at 04:30
  • Nice (fun) runtime check and interesting fact. Indeed for the full example I had in mind a runtime check would have been sufficient because at the end most of the function was the same for pointer and for arrays. In fact since the elements of one or the other are the same type the need for "genericity" of the function is only superficial. However, 1) compile time version seems appropriate still if the return type depends on the argument type (array or pointer). 2) you are using templates anyway in the function `is_array`. – alfC Jan 28 '15 at 06:44
  • @alfC I am using templates because this allows to check pointers of any type. If you just have one type you can easily remove the template. – DarioP Jan 28 '15 at 14:05
1

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.

marczellm
  • 1,224
  • 2
  • 18
  • 42
  • 1
    It works as long as the arguments are non `const` (see @Thomas comment), which is one important case I wanted to cover (because of the constness of string literals). – alfC Jan 28 '15 at 10:40
  • @alfC My solution prints "array" when invoked on a string literal. Is that not what you expected? What is a case that my solution fails at? – marczellm Jan 29 '15 at 15:20
  • Yes it does, but it is ambiguous if the array is `const`, I wanted to make it robust with respect to that as well. – alfC Jan 29 '15 at 21:01
0

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
alfC
  • 14,261
  • 4
  • 67
  • 118
  • Interesting solution. But it is an overkill because the example can be made to work without the ellipsis by adding `const&` to the first function. In which case reduces to the other solutions, `template T _thing1(T* const& x) { cout << "Pointer, x=" << x << endl; return x[0]; } template T _thing1(T (&x)[N]) { cout << "Array, x=" << x << ", N=" << N << endl; return x[0]; } template auto thing1(T&& x) -> decltype(_thing1(std::forward(x))) { return _thing1(std::forward(x)); }` – alfC Aug 04 '17 at 02:42