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.
double const d[] = {1.,2.,3.};
double a;
double const* p = &a;
f(d); // call array version
f(p); // call pointer version
I find this particularly difficult because of the well known fact that arrays decay to pointer sooner than later. A naive approach would be to write
void f(double const* c){...}
template<size_t N> void f(double const(&a)[N]){...}
Unfortunately this doesn't work. Because in the best case the compiler determines the array call f(d)
above to be ambiguous.
Partial solution:
I tried many things and the closest I could get was the following concrete code. Note also that, in this example code I use char
instead of double
, but it is very similar at the end.
First, I have to use SFINAE to disable conversions (from array ref to ptr) in the pointer version of the function. Second I had to overload for all possible arrays sizes (manually).
[compilable code]
#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
}
This works, but the problem is that I have to repeat the function for all possible values of N
and using template<size_t N>
gets me back to square zero, because with the template parameter the two calls get back to equal footing. In other works template<size_t N> void f(char const(&a)[N]){std::cout << "const arr" << std::endl;}
doesn't help.
Is there any way to generalize the second overload without falling back to an ambiguous call? or is there some other approach?
A C++ or C++1XYZ answer is also welcome.
Two details: 1) I used clang
for the experiments above, 2) the actual f
will end up being an operator<<
, I think know if that will matter for the solution.
Summary of solutions (based on other people's below) and adapted to a the concrete type char
of the example. Both seem to depend on making the char const*
pointer less obvious for the compiler:
1) One weird (portable?), (from the comment of @dyp.) Adding a reference qualifier to the pointer version:
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) One elegant (special case from @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;}
See Question&Answers more detail:
os