[英]Is it possible to overload a function that can tell a fixed array from a pointer?
动机:
几乎是为了好玩,我试图编写一个函数重载,它可以区分参数是固定大小的数组还是指针。
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;}
这似乎对我有用
#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"; }
显然这不是万无一失的。
您可以使用以下内容:
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>{}();
}
我喜欢使用标签调度:
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>>{} );
}
这是一个简单的解决方案,它利用了这样一个事实,即在 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);
但是,由于您是为了乐趣而编码,因此这可能是一种有趣的、基本的、第一种方法。
在我看来,这很简单:
#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
和魔法的所有复杂性。 如果我的方法有什么问题,请告诉我。
我也想知道如何绕过编译器抱怨不明确的重载并遇到了这个问题。
这个 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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.