【问题标题】:Is it possible to overload a function that can tell a fixed array from a pointer?是否可以重载可以从指针告诉固定数组的函数?
【发布时间】:2015-03-26 19:08:02
【问题描述】:

动机:

几乎是为了好玩,我正在尝试编写一个函数重载,它可以区分参数是固定大小的数组还是指针。

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&lt;size_t N&gt; 让我回到零平方,因为使用模板参数,两个调用回到平等的地位. 换句话说,template&lt;size_t N&gt; void f(char const(&amp;a)[N]){std::cout &lt;&lt; "const arr" &lt;&lt; std::endl;} 没有帮助。

有什么方法可以概括第二个重载而不回退到模棱两可的调用?还是有其他方法?

也欢迎提供 C++ 或 C++1XYZ 答案。

两个细节:1) 我在上面的实验中使用了clang,2) 实际的f 最终将成为operator&lt;&lt;,我想知道这对解决方案是否重要。


解决方案总结(基于其他人的以下)并适应示例的具体类型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;}
  1. 一个优雅的(来自@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;}

【问题讨论】:

  • std::array代替原始数组怎么样?
  • @user657267,谢谢,我只想要一个不涉及更改主函数中的代码的解决方案。如果您想知道,原因是我想从指针产生的文本中分辨出硬编码文本 ("hello")。
  • 有一个奇怪的解决方法:coliru.stacked-crooked.com/a/37214a2859f7e78d 一个更明显的解决方案是使用标签调度。
  • 你可以根据常量重载吗?
  • @Thomas,我在main 代码中尝试过,两者都是const,所以我认为无法利用它

标签: c++ arrays pointers c++11


【解决方案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)使用右值引用

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

显然这不是万无一失的。

【讨论】:

  • 太好了,你明白了,只要从参数类型中明确删除指针,衰减就不会发生。我在问题中发布了我原始代码风格的解决方案(特别是char 类型。)
  • 评论您的编辑:实际上这不是问题,请参阅我上面的解决方案摘要。 (我把总结给出了一个基于char-only 的重载的具体案例)
  • 注: std::experimental 中的内容不是“c++1z”或任何其他草案/未来标准的一部分,出于某种原因,它们位于单独的命名空间中。
  • @JonathanWakely 查看N3932 我不确定_v 类型是否应该在experimental 中,它们不是直接添加到库中吗?
  • @user657267 这就是提案所说的,但这不是委员会批准的内容。它们没有被添加到 C++ 工作文件中,它们进入了 Library Fundamentals TS 以及命名空间 std::experimental
【解决方案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>{}();
}

Live example

【讨论】:

  • 谢谢,您的解决方案可能是最“正式”的解决方案。两个 cmets:1) 成员函数可以是 static(例如称为 doit)和 2) 特化可以是具体类型(在我的情况下为 char),如 struct helper&lt;char const*&gt;......size_t N&gt; struct helper&lt;char const[N]&gt;...
  • 这看起来更漂亮。它更像是模式匹配,而不是 std::enable_if 中的 if 语句。
【解决方案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>>{} );
}

live example

【讨论】:

  • 谢谢,我猜第二个函数中的模板T 是不需要的,如果你只希望这个函数用于char。 template&lt;size_t N&gt; void foo(char const(&amp;)[N], std::false_type){...}
【解决方案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); 

但是,由于您是为了好玩而编写代码,因此这可能是一种有趣的、基本的、首选的方法。

【讨论】:

  • 目标是调用不同的重载函数(意味着它必须在编译时解析)......不确定你的is_array 是否算作,即使你声明它constexpr
  • @MattMcNabb 我同意你的看法,我不确定目标是创建重载还是只是从指针告诉数组...
  • 不错(有趣)的运行时检查和有趣的事实。实际上,对于完整的示例,我想到运行时检查就足够了,因为最后大多数函数对于指针和数组都是相同的。事实上,由于一个或另一个的元素是相同类型的,因此对函数“通用性”的需求只是表面的。但是,如果返回类型取决于参数类型(数组或指针),1)编译时版本似乎仍然合适。 2)你在函数is_array中使用模板。
  • @alfC 我正在使用模板,因为这允许检查任何类型的指针。如果您只有一种类型,则可以轻松删除模板。
【解决方案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_ifstd::true_typestd::is_pointer 和魔法的所有复杂性。如果我的方法有什么问题,请告诉我。

【讨论】:

  • 只要参数不是const(参见@Thomas 评论),它就可以工作,这是我想介绍的一个重要案例(因为字符串文字的常量)。
  • @alfC 我的解决方案在对字符串文字调用时打印“array”。这不是你所期望的吗?我的解决方案在什么情况下失败?
  • 是的,但如果数组是 const 则不明确,我也想让它变得健壮。
【解决方案6】:

我也想知道如何绕过编译器抱怨模棱两可的重载并遇到了这个问题。

这个 C++11 解决方案在形式上与调度答案相似,但使用了可变参数 SFINAE 技巧(编译器首先尝试数组版本)。 “decltype”部分允许不同的返回类型。如果所需的返回类型是固定的(例如,根据 OP 的“void”),则不需要。

#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

【讨论】:

  • 有趣的解决方案。但这是一个矫枉过正的做法,因为可以通过在第一个函数中添加 const&amp; 来使该示例在没有省略号的情况下工作。在这种情况下,减少到其他解决方案,template &lt;typename T&gt; T _thing1(T* const&amp; x) { cout &lt;&lt; "Pointer, x=" &lt;&lt; x &lt;&lt; endl; return x[0]; } template &lt;typename T, unsigned N&gt; T _thing1(T (&amp;x)[N]) { cout &lt;&lt; "Array, x=" &lt;&lt; x &lt;&lt; ", N=" &lt;&lt; N &lt;&lt; endl; return x[0]; } template &lt;typename T&gt; auto thing1(T&amp;&amp; x) -&gt; decltype(_thing1(std::forward&lt;T&gt;(x))) { return _thing1(std::forward&lt;T&gt;(x)); }
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多