2015-01-28 2 views
19

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

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

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

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

void f(char const* c){...} 
template<size_t N> void f(char 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;} 
+0

Как об использовании 'зОго :: array' вместо исходных массивов? – user657267

+0

@ user657267, спасибо, я просто хочу решение, не связанное с изменением кода в основной функции. Причина, по которой вы хотите знать, - это то, что я хочу передать жесткий текст («привет») из текста, полученного в результате указателя. – alfC

+3

Есть странная работа: http://coliru.stacked-crooked.com/a/37214a2859f7e78d Более очевидным решением является использование отправки тегов. – dyp

ответ

21

Это, кажется, сработает для меня

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

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

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

Очевидно, что это не является надежным.

+0

Отлично, вы получили его, как только один удаляет указатель явно из типа аргумента, распад не происходит. Я отправил в вопрос решение в стиле моего исходного кода (для типов 'char' в частности.) – alfC

+0

Комментарий к вашему правлению: на самом деле это не проблема, см. Мое резюме решений выше. (Я поставил резюме, чтобы дать конкретный случай перегрузки, основанного только на 'char') – alfC

+0

N.B. вещи в 'std :: experimental' не являются частью« C++ 1z »или любого другого проекта/будущего стандарта, они по отдельности находятся в отдельном пространстве имен. –

9

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

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

Спасибо, ваше решение, вероятно, является наиболее« формальным ». Два комментария: 1) функция-член может быть 'static' (например, называется' doit') и 2) специализация может быть для конкретного типа (в моем случае 'char'), как в' struct helper ... ' и '... size_t N> struct helper ...' – alfC

+1

Это кажется более красивым. Это скорее похоже на сопоставление шаблонов вместо оператора 'if', как в' std :: enable_if'. –

6

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

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

+1

спасибо, я думаю, что шаблон 'T' во второй функции не нужен, если требуется только для char. 'template void foo (char const (&) [N], std :: false_type) {...}' – alfC

4

Вот простое решение, которое использует тот факт, что в то время как in C the value of an array is equal to its address это, как правило, не относится к указателю.

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

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

+0

Цель состоит в том, чтобы вызвать другую перегруженную функцию (то есть она должна быть разрешена во время компиляции) ... не уверен, что ваш 'is_array' считается таким, даже если вы его объявляете' constexpr' –

+0

@MattMcNabb Я согласен с вами, я не был уверен, что целью было создание перегрузок или просто указать массив из указателя ... – DarioP

+0

Хорошая (забавная) проверка времени выполнения и интересный факт. Действительно, для полного примера я имел в виду, что проверка времени выполнения была бы достаточной, поскольку в конце большая часть функции была одинаковой для указателя и для массивов. На самом деле, поскольку элементы того или другого одного типа, потребность в «общности» функции является лишь поверхностной. Однако 1) время компиляции кажется подходящим, если тип возврата зависит от типа аргумента (массив или указатель). 2) вы все равно используете шаблоны в функции 'is_array'. – alfC

1

На мой взгляд, это так просто, как это:

#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 и магии. Если что-то не так с моим подходом, скажите мне.

+1

Он работает до тех пор, пока аргументы не являются 'const' (см. Комментарий @Thomas), что является одним из важных случаев, которые я хотел рассмотреть (из-за константы строковых литералов). – alfC

+0

@alfC Мое решение печатает «массив» при вызове в строковом литерале. Разве это не то, что вы ожидали? Каков случай, когда мое решение не удается? – marczellm

+0

Да, но это неоднозначно, если массив является 'const', я хотел бы сделать его надежным относительно этого. – alfC

0

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

Это решение C++ 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 
+0

Интересное решение. Но это избыток, потому что пример можно заставить работать без эллипсиса, добавив 'const &' к первой функции. В этом случае сводится к другим решениям, 'template T _thing1 (T * const & x) {cout <<" Указатель, x = "<< x << endl; return x [0]; } шаблон T _thing1 (T (& x) [N]) {cout << "Массив, x =" << x << ", N =" << N << endl; return x [0]; } template auto thing1 (T && x) -> decltype (_thing1 (std: forward (x))) {return _thing1 (std: forward (x)); } ' – alfC