Is it possible to overload a function that can tell a fixed array from a pointer?

前端 未结 6 1821
感动是毒
感动是毒 2020-12-05 14:06

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.

相关标签:
6条回答
  • 2020-12-05 14:28

    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

    0 讨论(0)
  • 2020-12-05 14:30

    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.

    0 讨论(0)
  • 2020-12-05 14:33

    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

    0 讨论(0)
  • 2020-12-05 14:44

    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.

    0 讨论(0)
  • 2020-12-05 14:45

    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.

    0 讨论(0)
  • 2020-12-05 14:45

    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
    
    0 讨论(0)
提交回复
热议问题