Можно ли перегрузить функцию, которая может указывать фиксированный массив из указателя?

Мотивация:

Почти ради интереса я пытаюсь написать перегрузку функции, которая может отдельно отличить, является ли аргумент массивом фиксированного размера или указателем.

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

Я нахожу это особенно трудным из-за хорошо известного факта, что массивы распадаются на указатель раньше, чем позже. Наивным подходом было бы написать

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

К сожалению, это не работает. Потому что в лучшем случае компилятор определяет вызов массива f(d) выше как неоднозначный.

Частичное решение:

Я перепробовал много вещей, и ближе всего я мог получить следующий конкретный код. Также обратите внимание, что в этом примере кода я использую char вместо double, но в конце он очень похож.

Во-первых, я должен использовать SFINAE, чтобы отключить преобразования (из массива ref в ptr) в версии указателя функции. Во-вторых, мне пришлось перегрузить все возможные размеры массивов (вручную).

[компилируемый код]

#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
}

Это работает, но проблема в том, что мне нужно повторить функцию для всех возможных значений N, и использование template<size_t N> возвращает меня к нулю, потому что с параметром шаблона два вызова возвращаются в равную основу, В других работах template<size_t N> void f(char const(&a)[N]){std::cout << "const arr" << std::endl;} не помогает.

Есть ли способ обобщить вторую перегрузку, не возвращаясь к неоднозначному вызову? или есть какой-то другой подход?

Ответ C++ или C++ 1XYZ также приветствуется.

Две детали: 1) я использовал clang для экспериментов выше, 2) фактический f в итоге станет operator<<, я думаю, знаю, будет ли это иметь значение для решения.


Краткое изложение решений (основано на других людях ниже) и адаптировано к конкретному типу char примера. Похоже, что оба они делают указатель char const* менее очевидным для компилятора:

1) Один странный (переносимый?) (Из комментария @dyp.) Добавление ссылочного квалификатора к версии указателя:

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;}

2) Один элегантный (особый случай от @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;}

Ответ 1

Кажется, это работает для меня

#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");
}

Бонус std::experimental::type_traits:

using std::experimental::is_pointer_v;
std::enable_if_t<is_pointer_v<T>>

Ваш комментарий заставил меня попробовать что-то еще проще

template<typename T> void foo(T) { std::cout << "pointer\n"; }
template<typename T, unsigned sz> void foo(T(&)[sz]) { std::cout << "array\n"; }

Конечно, проблема здесь в том, что foo теперь можно вызывать для любого типа, зависит от того, насколько слабенько вы хотите, чтобы ваша проверка параметров была.


Другой способ: (ab) использовать ссылки rvalue

void foo(char const*&) { std::cout << "pointer\n"; }
void foo(char const*&&) { std::cout << "array\n"; }

Очевидно, что это не безупречно.

Ответ 2

Вы можете использовать следующее:

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>{}();
}

Живой пример

Ответ 3

Мне нравится использовать диспетчер отправки тегов:

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>>{} );
}

живой пример

Ответ 4

Вот простое решение, в котором используется тот факт, что в C значение массива равно его адресу, в то время как это обычно неверно для указателя.

#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");
}

Обратите внимание, что хотя указатель обычно указывает на место, отличное от самого, это не обязательно гарантируется; действительно, вы можете легко обмануть код, выполнив что-то вроде этого:

//weird nasty pointer that looks like an array:
int * z = reinterpret_cast<int*>(&z); 

Однако, поскольку вы кодируете удовольствие, это может быть интересным, базовым, первым подходом.

Ответ 5

По-моему, это так просто:

#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);
}

Я не получаю все сложности с std::enable_if и std::true_type и std::is_pointer и магией. Если что-то не так с моим подходом, скажите мне.

Ответ 6

Мне тоже было интересно, как обойти компилятор, жалующийся на неоднозначные перегрузки и упавший на него.

Это решение С++ 11 аналогично по форме для ответа на отправку, но вместо этого использует трианк с переменной версией SFINAE (компилятор сначала пытается получить версию массива). Часть "decltype" позволяет использовать разные типы возвращаемых данных. Если требуемый тип возвращаемого значения является фиксированным (например, "void" в соответствии с OP), то это не требуется.

#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;

}

Пример вывода:

$ 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