array decay to pointer and overload resolution - c++

I want to be able to differentiate array from pointers in overload resolution :
class string {
public:
string(const char* c_str);
template<int N>
string(const char (&str) [N]);
};
int main() {
const char* c_str = "foo";
string foo(c_str); // ok will call string(const char*)
string bar("bar"); // call string(const char*) instead of the array version
}
The best I have found so far is to use a reference to the pointer instead of a pointer :
class string {
public:
string(const char*& c_str);
template<int N>
string(const char (&str) [N]);
};
int main() {
const char* c_str = "foo";
string foo(c_str); // ok will call string(const char*)
string bar("bar"); // ok, will call the array version
}
it's not exactly the same thing and I want to know if a better way exist

You need to make the first overload a poorer choice when both are viable. Currently they are a tie on conversion ranking (both are "Exact Match"), and then the tie is broken because non-templates are preferred.
This ought to make the conversion ranking worse:
struct stg
{
struct cvt { const char* p; cvt(const char* p_p) : p(p_p) {} };
// matches const char*, but disfavored in overload ranking
stg(cvt c_str); // use c_str.p inside :( Or add an implicit conversion
template<int N>
stg(const char (&str) [N]);
};

You can use SFINAE. This might not be the best way, but it should work ok:
//thanks to dyp for further reduction
template<typename T, typename = typename std::enable_if<std::is_same<T, char>::value>::type>
string(const T * const &) {std::cout << "const char *\n";}
template<std::size_t N> //credit to jrok for noticing the unnecessary SFINAE
string(const char(&)[N]) {std::cout << "const char(&)[" << N << "]\n";}
Here's a live example.

A more generic version of this problem could be detected as follows.
template <class T>
void func(T,
typename std::enable_if<std::is_pointer<T>::value, void>::type * = 0)
{
// catch ptr
}
template <class T, int N>
void func(T (&)[N])
{
//catch array
}
int main(void)
{
int arr[5];
char *b = 0;
func(arr); // catch array
func(b); // catch ptr
}

Related

function template explicit specializatioin

I'm learning c++ template and i have a question.
#include <iostream>
#include <string>
using namespace std;
template <typename T>
size_t GetStringSize(const T& s) { return s.size(); }
template<>
size_t GetStringSize(const char* s) { return s.strlen(); }
template<typename T,typename... types>
size_t GetStringSize(const T& arg, types... args) {
return GetStringSize(arg) + GetStringSize(args...);
}
int main() {
int n = GetStringSize("faf");
cout << n << endl;
return 0;
}
template<> size_t GetStringSize(const char* s) { return s.strlen(); }
in here, i got error
Error E0493 : no instance of overloaded function "GetStringSize" matches the specified type.
i think there is nothing wrong with using explicit specialization.
what's wrong with this?
help me please..
Problem #1: You are missing the include that declares strlen. Your compiler may allow this omission, but the next version may not!
#include <cstring>
Problem #2: You are misusing strlen syntax.
return strlen(s);
Problem #3: const char* cannot be a specialization of const T& because it is not a reference and is not const (it's a non-const pointer to something const).
template<>
size_t GetStringSize(const char*const & s) { return strlen(s); }
Problem #4: "faf" has the type const char[4], so you are going to get the wrong match anyway. You probably want a new template rather than a specialization.
template <size_t N>
size_t GetStringSize(const char(&s)[N]) { return strlen(s); }
// For raw pointers
size_t GetStringSize(const char* s) { return strlen(s); }

Function template argument deduction with user-defined conversion operator

Let's say I have a class that wraps a string literal:
template <size_t N>
class literal {
public:
constexpr literal(const char(&s)[N+1]) : wrapped_(s) {}
constexpr const char * c_str() const { return wrapped_; }
constexpr size_t size() const { return N; }
private:
const char (&wrapped_)[N+1];
};
template <size_t N>
literal<N-1> make_literal(const char (&s)[N]) { return literal<N-1>(s); }
Now, I'd like for instances of this wrapped string type to be convertible back to a const char[N] implicitly, in a way I can still access its size. I'd like to be able to do something like:
template <size_t N>
void foo(const char(&s)[N]) {
std::cout << N << ": " << s << std::endl;
}
int main() {
constexpr auto s = make_literal("testing");
foo(s);
}
My goal is to have one function defined for foo() that can accept actual string literals as well as wrapped literals. I've tried adding a user-defined conversion operator to the class definition:
using arr_t = char[N+1];
constexpr operator const arr_t&() const { return wrapped_; }
But this gives me the following with clang:
candidate template ignored: could not match 'const char [N]' against 'const literal<7>'
If I change the call to foo() to the following, it works:
foo((const char(&)[8])s);
...which means that the conversion operator works, but not in the context of template argument deduction. Is there any way I can make this work without defining foo() specifically to take a wrapped literal?
The problem you are having is templates never do conversions of the parameters. Since you give it a const literal<7>, that is all it has to work with.
The easy fix for this is to add an overload and then do the cast in the overload to call your string literal version. That should look like
template <size_t N>
void foo(const literal<N> &lit) {
foo(static_cast<typename literal<N>::arr_t&>(lit)); // explicitly cast to the array type alias
}
which gives you a full example of
template <size_t N>
class literal {
public:
constexpr literal(const char(&s)[N+1]) : wrapped_(s) {}
constexpr const char * c_str() const { return wrapped_; }
constexpr size_t size() const { return N; }
using arr_t = const char[N+1]; // <- Add const here since literals are const char[N]
constexpr operator const arr_t&() const { return wrapped_; }
private:
const char (&wrapped_)[N+1];
};
template <size_t N>
constexpr literal<N-1> make_literal(const char (&s)[N]) { return literal<N-1>(s); }
template <size_t N>
void foo(const char(&s)[N]) {
std::cout << N << ": " << s << std::endl;
}
template <size_t N>
void foo(const literal<N> &lit) {
foo(static_cast<typename literal<N>::arr_t&>(lit)); // explicitly cast to the array type alias
}
int main() {
constexpr auto s = make_literal("testing");
foo(s);
}
Yes, you are adding an overload but all of the important code does not have to be duplicated.
If you can use C++17 and you don't mind a little indirection you could do all of this with one function using a std::string_view and providing literal with a operator std::string_view. That would look like
template <size_t N>
class literal {
public:
constexpr literal(const char(&s)[N+1]) : wrapped_(s) {}
constexpr const char * c_str() const { return wrapped_; }
constexpr size_t size() const { return N; }
using arr_t = const char[N+1];
constexpr operator std::string_view() const { return wrapped_; }
private:
const char (&wrapped_)[N+1];
};
template <size_t N>
constexpr literal<N-1> make_literal(const char (&s)[N]) { return literal<N-1>(s); }
void foo(std::string_view s) {
std::cout << s.size() << ": " << s << std::endl;
}
int main() {
constexpr auto s = make_literal("testing");
foo(s);
foo("testing");
}

How to avoid decay with template parameter deduction

Simplified:
// CHAR_TYPE == char, wchar_t, ...
template <typename CHAR_TYPE, unsigned CHAR_COUNT>
void Foo(CHAR_TYPE const (&value)[CHAR_COUNT]) noexcept
{
TRACE("const ref array");
// perform a bit of logic and forward...
}
template <typename CHAR_TYPE>
void Foo(CHAR_TYPE const* value) noexcept
{
TRACE("const ptr");
// perform a bit of logic and forward...
}
// [ several other overloads ]
Callsite:
char const* ptr = ...
wchar_t const* wptr = ...
Foo(ptr); // <-- good: "const ptr"
Foo(wptr); // <-- good: "const ptr"
constexpr char const buffer[] { "blah blah blah" };
constexpr wchar_t const wbuffer[] { L"blah blah blah" };
Foo(buffer); // <-- ambiguous
Foo(wbuffer); // <-- ambiguous
Of course, I could remove the const ref array overload. However I would like to handle these types differently. I have tried to conditionally enable the correct overload, but I have not been able to determine the necessary condition.
template <typename CHAR_TYPE, unsigned COUNT>
typename std::enable_if</* std::is_?? */, void>::type
Foo(CHAR_TYPE const (&value)[COUNT]) noexcept
{
TRACE("by ref array");
// perform a bit of logic and forward...
}
template <typename CHAR_TYPE>
typename std::enable_if</* std::is_?? */, void>::type
Foo(CHAR_TYPE const* value) noexcept
{
TRACE("ptr");
// perform a bit of logic and forward...
}
What is the best way to disambiguate these overloads?
(I would prefer not to use an array wrapper)
One idea that works is to remove the pointer and simply have T instead, with a std::enable_if_t<std::is_pointer<T>::value> guard. Simplified example below:
#include <iostream>
#include <type_traits>
template<class T, size_t N>
void f(T const (&) [N])
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
template<class T, std::enable_if_t<std::is_pointer<T>::value>* = nullptr >
void f(T)
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
int main()
{
const char* str = "test";
char str2[]{"test2"};
f(str);
f(str2);
}
Live on Coliru
Taking the argument by (const) reference blocks array-to-pointer decay during template argument deduction. See [temp.deduct.call]/2. So:
template <typename CHAR_TYPE>
void Foo(CHAR_TYPE const* const & value) noexcept
{
TRACE("const ptr");
// perform a bit of logic and forward...
}

Function template specialization type - is it optional?

Is the <const char*> optional in below code? I found that g++ and clang compiles without it just fine.
template<typename T>
void debugRep2(T const& t) {
std::cout << "debugRep(const T& t)\n";
}
template<>
void debugRep2<const char*>(const char* const& t) {
//^^^^^^^^^^^^^
std::cout << "const char*& t\n";
}
int main() {
int n;
int *pn = &n;
debugRep2(n);
debugRep2(pn);
}
The templated type is already specified at the function parameter and can be deduced by the compiler
template<>
void debugRep2<const char*>(const char* const& t) {
// ^^^^^^^^^^^ already present
// ...
}
So yes, in this case it is optional.
In fact the common way to write that specialization would be
template<>
void debugRep2(const char* const& t) {
// ...
}

const char array (c style string) template specialization

I want to be able to specialize based on a constant c style string. The problem is that when I call my templatized function the type is const char[N] where 'N' is the size of the string +1 (null character). How can I specialize for all c-style strings?
The following code displays the problem. You can see the specialization for const char [15] gets matched for "const char [15]" but for "const char[5]" it goes to Generic.
Is there any way to do this?
template <typename T>
struct Test {
static const char* type() { return "Generic"; }
};
template <>
struct Test<const char*> {
static const char* type() { return "const char*"; }
};
template <>
struct Test<const char[]> {
static const char* type() { return "const char[]"; }
};
template <>
struct Test<const char[15]> {
static const char* type() { return "const char[15]"; }
};
template <>
struct Test<char*> {
static const char* type() { return "char*"; }
};
template <>
struct Test<char[]> {
static const char* type() { return "char[]"; }
};
template <typename T>
void PrintType(const T& expected) {
std::cerr << expected << " type " << Test<T>::type() << std::endl;
}
int main(int argc, char* argv[]) {
const char* tmp = "const char*";
PrintType(tmp);
PrintType("const char[]");
PrintType("const char[15]");
PrintType("const char[5]");
}
output when run in Windows 7 - VS 2008
const char* type const char*
const char[] type Generic
const char[15] type const char[15]
const char[5] type Generic
A specialization for any array of chars is:
template< std::size_t N >
struct Test< const char[N] > { ... };
However you can no longer return a char* from type(), unless you write more metafunctions to turn a non-type template parameter into its textual representation.
Try this:
#include <cstdio>
#include <string>
template<class T>
void f2(const T& s) // Handle all kinds of string objects
{ std::printf("string object: %s\n", s.c_str()); }
void f2(const char* s) // Handle const char*
{ std::printf("const char*: %s\n", s); }
// ----------------------------------------------------------------------------
template<size_t N>
void f(const char(&s)[N]) // Handle const char array
{ std::printf("const char[%zu]: %s\n", N, s); }
template<size_t N>
void f(char(&s)[N]) // Handle char array
{ std::printf("char[%zu]: %s\n", N, s); }
template<class T>
inline void f(T&& s) // Handle other cases
{ f2(std::forward<T>(s)); }
int main() {
std::string stringObj = "some kind of string object ...";
char charArr[] = "char array";
const char constCharArr[] = "const char array";
const char* constCharPtr = "const char pointer";
f(stringObj);
f(charArr);
f(constCharArr);
f(constCharPtr);
//f("const char array");
}
Output:
string object: some kind of string object ...
char[11]: char array
const char[17]: const char array
const char*: const char pointer
Explanation
f() has two kinds of overloads: One for char arrays and one for "everything else".
f2() handles the "everything else" case.