I'm playing around with overloading operators in c++14, and I tried to match two types of arguments: any-old-const-char*, and a-string-literal.
That is, I'm trying to see if I can discriminate between:
const char * run_time;
and
"compile time"
I wrote the code below, and as shown, when I try span >> "literal" it invoked the const char* function.
When I #if 0-out the const char* version, the template version gets called just fine.
If I change the template version to take an rvalue-reference (&&) parameter for literal, it doesn't compile.
If I add a const char (&literal)[] non-template version, the const char* version is still preferred. Removing the const-char* version, the template version is preferred.
Can you explain this? In particular:
Why is const char* preferred over const char (&)[N]?
Why is const char (&)[N] preferred over const char (&)[] (non-template)?
Why is const char (&&)[N] unable to compile?
Is there a "right way" to capture literal strings?
Thanks.
#include <iostream>
using namespace std;
#include <gsl/gsl>
#include <type_name.h++>
template<unsigned N>
auto
operator>>(gsl::span<const char*,-1>& spn, const char (&literal)[N])
-> gsl::span<const char*, -1>&
{
cout << "Got array: " << literal << endl;
return spn;
}
auto
operator>>(gsl::span<const char*,-1>& spn, const char *literal)
-> gsl::span<const char*, -1>&
{
cout << "Got const-char*: " << literal << endl;
return spn;
}
#if 0
#endif
int
main(int argc, const char *argv[])
{
auto spn = gsl::span<const char*>(argv, argc);
cout << type_name<decltype(spn)>() << endl; // gsl::span<const char *, -1>
cout << type_name<decltype("literal")>() << endl; // char const (&)[8]
cout << type_name<decltype(("literal"))>() << endl; // char const (&)[8]
auto helpx = "literal";
cout << type_name<decltype(helpx)>() << endl; // const char *
spn >> "literal"; // Got const-char*: literal
return 0;
}
Edit:
In case it matters, I'm compiling with:
c++ --std=c++14 -Iinclude -c -o main.o main.c++
And c++ says:
$ c++ --version
Apple LLVM version 8.0.0 (clang-800.0.42.1)
Target: x86_64-apple-darwin16.5.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
Why is const char* preferred over const char (&)[N]?
The reason for this is rather technical. Even though the decay of a string literal from const char[N] to const char* is a conversion, it falls into the "lvalue transformation" category and is therefore considered by [over.ics.rank]/3 to be as good as no conversion at all. Since "no conversion" is required for either overload, the non-template overload wins.
Why is const char (&)[N] preferred over const char (&)[] (non-template)?
It is not possible to bind a reference to array of unknown bound to a value of type array of known bound. Instead, a reference to array of unknown bound can only be bound to values that are themselves arrays of unknown bound.
Why is const char (&&)[N] unable to compile?
A string literal is an lvalue so I'm not sure why you would expect this to work.
Is there a "right way" to capture literal strings?
You can use a helper function template that captures its argument using a forwarding reference so as to not destroy any type information (const char* versus const char[N]) then dispatch on the type using template specialization. You'll probably also want to use SFINAE to make sure it is disabled if anything other than a const char* or const char[N] is passed in. To wit,
template <bool b>
struct f_helper;
template <>
struct f_helper<true> {
void do_it(const char*) {
puts("pointer");
}
};
template <>
struct f_helper<false> {
template <std::size_t N>
void do_it(const char (&)[N]) {
printf("array of length %zd\n", N);
}
};
template <class T, class = typename std::enable_if<std::is_same<char*, std::decay_t<T>>::value ||
std::is_same<const char*, std::decay_t<T>>::value>::type>
void f(T&& s) {
f_helper<std::is_pointer<std::remove_reference_t<T>>::value>{}.do_it(s);
}
Coliru link: http://coliru.stacked-crooked.com/a/0e9681868d715e87
The overload taking a pointer is preferred because it is not a template according to
13.3.3 Best viable function [over.match.best]
Given these definitions, a viable function F1 is defined to be a better function than another viable function
F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then
...
(1.7)
F1 is not a function template specialization and F2 is a function template specialization
actually non-template const char (&)[] does not seem to compile at all because it is a reference to a non-bound array. It is possible to pass a pointer like this const char [], but not array.
this should fail at least for the same reason as (2)
you can provide another template taking a reference to pointer:
template< typename = void > void
foo(char const * & text)
{
::std::cout << "got ptr" << ::std::endl;
}
Note that providing another template taking a pointer won't work because both template specializations will be fine and we'll get ambiguous choice of overloaded functions.
Related
Hello I have this code from C++ primer 5th ed:
Primary function template:
// first version; can compare any two types
template <typename T>
int compare(T const& x, T const& y)
{
std::cout << "compare(T const&, T const&)\n";
if(std::less<T>()(x, y))
return -1;
if(std::less<T>()(y, x))
return 1;
return 0;
}
A specialization for character arrays:
// second version to handle string literals
template <unsigned N, unsigned M>
int compare(char const(&ar1)[N], char const(&ar2)[M])
{
std::cout << "compare(char const(&)[N], char const(&)[M])\n";
return strcmp(ar1, ar2);
}
// special version of compare to handle pointers to character arrays
template <>
int compare(const char* const &p1, const char* const &p2)
{
std::cout << "compare(char const* const&, char const* const&)\n";
return strcmp(p1, p2);
}
int main()
{
const char *p1 = "hi", *p2 = "mom";
compare(p1, p2); // calls the third version (pointers to character strings)
compare("hi", "mom"); // calls the template with two nontype parameters
compare("high", "HIGH"); // error: call ambiguous
std::cout << "\nDone!\n";
}
I have some questions:
Is the version of compare with reference to arrays parameters a specialization or an overload? I think it is a specialization because its parameter list must match the one of the Primary function template compare. is it right?
The program works fine until I pass two arrays of characters or two literal character string with Same lengths. in which case the compiler cannot resolve the call like in my call compare("high", "HIGH");.:
Does this mean it fails because the version with arrays parameters is not viable? -because I guess that the Size of an array is a part of its type thus passing two arrays with different sizes yields two different types consequently this version is not viable?
The output of my compiler:
error: call of overloaded ‘compare(const char [5], const char [5])’ is ambiguous
candidate: ‘int compare(const T&, const T&) [with T = char [5]]’|
candidate: ‘int compare(const char (&)[N], const char (&)[M]) [with unsigned int N = 5; unsigned int M = 5]’
So how could I disambiguate this call? and please guide me about my guesses. Thanks
With respect to your first question: the function is an overload and not a specialization. There is no way to partially specialized a function template to start with. Beyond that, a specialization would mention an empty template parameter list. For example, the version for char const* is a specialization:
template <>
int compare(char const* x, char const& *);
With respect to your second question: it seems the compiler came to the conclusion that the first overload and the second overload are equally good. I'm not quite sure why that is as the version taking array references seems better. Adding another overload resolves that problem:
template <unsigned N>
int compare(char const(&ar1)[N], char const(&ar2)[N])
{
std::cout << "compare(char const(&)[N], char const(&)[N])\n";
return strcmp(ar1, ar2);
}
The version with two array sizes would be viable as well. It is just that the other overload is good, too. Instead of adding an overload, you could constrain the first version so it can't be used for arrays:
template <typename T>
std::enable_if_t<!std::is_array_v<T>, int> compare(T const& x, T const& y)
{
std::cout << "compare(T const&, T const&)\n";
if(std::less<T>()(x, y))
return -1;
if(std::less<T>()(y, x))
return 1;
return 0;
}
I try to specialize a template function for a few types, one of them is const char*.
template<typename T = const char*>
void f(T&&);
template<>
void f<int>(int&& k) { std::cout << "Now in int fcn (" << k << ")!\n"; }
template<>
void f<const char*>(const char* && k) { std::cout << "Now in cc* fcn (" << k << ")!\n"; }
int main() {
f<int>(5);
f("HELLO");
return 0;
}
But when I execute f("HELLO"), I get the following error:
main.cpp:(.text+0x32): undefined reference to `void f<char const (&) [6]>(char const (&) [6])'
How do I make it interpret "HELLO" as a const char* and not an array? If I specialize for arrays I need one for each array size?
Also, the generic template function catches f("HELLO"):
template<typename T>
void f(T&& k) { /* Definition... */ }
Will it create one specialization for every array size I need or does it somehow cast the string literal to "const char*"?
A string literal is not a const char*. A string literal has the type const char[N] where N is the number of characters plus a null terminator. That means when you call the function T gets deduced to const char[6], which does not match any of the specializations so the main template is used. Since you have not defined the main template, you get a linker error about the definition missing.
You can add an overload of the function to handle char arrays and string literals by using
template<std::size_t N> void f(const char (&arr)[N]) { stuff; }
Yes, it will stamp out a function for each sized array, but that's just a little extra compilation time, you only need to write the body once.
You should also have a read of Why Not Specialize Function Templates?. In the article it details why function template specializations don't always work like you want them to and that they don't participate in overload resolution. Generally you should overload instead of specializing.
Re-edited:
Here's what C++ Primer 5th says:
Version 1:
template <typename T> int compare(const T&, const T&);
Version 2:
template<size_t N, size_t M> int compare(const char (&)[N], const char (&)[M]);
A specialization of Version 1:
template <> int compare(const char* const &p1, const char* const &p2);
For example, we have defined two versions of our compare function template, one that takes references to array parameters and the other that takes const T&. The fact that we also have a specialization for character pointers has no impact on function matching. When we call compare on a string literal: compare("hi", "mom")
both function templates are viable and provide an equally good (i.e., exact) match to the call. However, the version with character array parameters is more specialized (§ 16.3, p. 695) and is chosen for this call.
The book says "both provide an equally good match", so then I thought putting Version 1 and its specialization should compile well. But it didn't.
So "provide an equally good match" doesn't mean it can compile? The book plays a trick on me?
Original code snippet link that I didin't understand why can't compile:
https://wandbox.org/permlink/oSCDWad03nELC9xs
Full context screenshot (I've boxed the most related part, sorry to post such a big pic here).
C-style strings are not pointers, they are arrays. When template type deduction happens, it deduces T as either const char[3] or const char[4]. Since those conflict the compiler is unable to deduce T and it stops there.
template<>
int compare(const char* const &p1, const char* const&p2) {
cout << "const char* const" << endl;
return 3;
}
won't be called because it relies on T being deduced and matching const char* and the compiler was not able to deduce T. A specialization is not a overload, it is a recipe for that specific T. If T can't be deduced then the specialization, even it it were to be a valid overload, won't be called.
If you were to overload the function instead of providing a specialization then it would compile with:
int compare(const char* const &p1, const char* const&p2) {
cout << "const char* const" << endl;
return 3;
}
You are passing to the template function two parameters of different types (the type of "hi" is const char [3] and the type of "mom" is const char [4]), so the compiler is not able to find a T that matches both types.
It's the same error that you would obtain calling std::min(0, 1U); std::min() (one of its overload) expects two arguments of the same type, as your compare() function does.
A possible solution to your problem is to accept parameters of different types:
template <typename T1, typename T2>
int compare(const T1&, const T2&);
This will work without editing the body of your function.
The compiler is unable to match it to one of the existing templates. If you read the section 16.5 carefully you will understand that it would call the second version of the template class.
The function call has 2 different types of parameters const char[3] and const char [4] compiler is unable to find a template specialization that takes 2 different data types as parameters.
The code below is one of the solutions.
#include <iostream>
#include <string>
using namespace std;
template <typename T> int compare(const T&, const T&) {
cout << "const T" << endl;
return 3;
}
template<size_t N, size_t M>
int compare(const char (&p)[N], const char (&q)[M]) {
cout<<p<<" "<<q<<endl;
return 3;
}
int main()
{
compare("hi", "mom");
}
The other solution is as below. It takes 2 different types and access the variables.
#include <iostream>
#include <string>
using namespace std;
template <typename T> int compare(const T&, const T&) {
cout << "const T" << endl;
return 3;
}
template <typename T1, typename T2>
int compare(const T1&p, const T2&q){
cout<<p<<" "<<q<<endl;
return 3;
}
int main()
{
compare("hi", "mom");
}
In this line:
auto a = "Hello World";
What is the exact Type of a? I'd guess char[] or const char* const but I'm not sure.
N4296 2.13.5/8
Ordinary string literals and UTF-8 string literals are also referred
to as narrow string literals. A narrow string literal has type “array
of n const char”, where n is the size of the string as defined below,
and has static storage duration (3.7).
But since variable is initialized as in your code it is actually const char*, you can check it like this.
template<typename> struct TD;
int main()
{
auto a = "Hello World";
TD<decltype(a)> _;
}
Here will be compile error in which you can see the actual type of TD instance, something like this with clang
error: implicit instantiation of undefined template 'TD<const char *>'
N4296 7.1.6.4
If the placeholder is the auto type-specifier, the deduced type is
determined using the rules for template argument deduction.
template<typename> struct TD;
template<typename T>
void f(T)
{
TD<T> _;
}
int main()
{
auto c = "Hello";
TD<decltype(c)> _;
f("Hello");
}
Both instantiated objects of type TD has type TD<const char*>.
N4926 14.8.2.1
Template argument deduction is done by comparing each function
template parameter type (call it P) with the type of the corresponding
argument of the call (call it A) as described below.
If P is not a reference type:
If A is an array type, the pointer type produced by the
array-to-pointer standard conversion (4.2) is used in place of A for
type deduction
Unless you've reason to think it'd be implementation or un-defined, can just test:
#include <iostream>
template <typename T> void f() { std::cout << "other\n"; }
template <> void f<const char*>() { std::cout << "const char*\n"; }
template <> void f<const char* const>()
{ std::cout << "const char* const\n"; }
template <> void f<const char(&)[12]>() { std::cout << "const char[12]\n"; }
int main()
{
auto a = "Hello World";
f<decltype(a)>();
}
Output:
const char*
Checking that ++a compiles is another clue (it does), and while implementation defined #include <typeinfo> / typeid(a).name() can often help answer such questions.
Change to auto& a and you'll see a changes to const char(&)[12].
You can print the type of a using typeinfo
int main()
{
auto a = "Hello World";
std::cout << "type is: " << typeid(a).name() << '\n';
}
on gcc it will print
pi is: PKc
which stands for pointer to constant char
If you're in Windows the output will be a lot more readable, but you get used to this syntax too.
If you know more or less which type you a re looking for, you can also check if two types are equivalent with:
#include <typeinfo>
std::cout << std::is_same<const char*, decltype(a)>::value << std::endl;
Can any one help me understand the following code
#include <iostream>
void foo(const char * c)
{
std::cout << "const char *" << std::endl;
}
template <size_t N>
void foo(const char (&t) [N])
{
std::cout << "array ref" << std::endl;
std::cout << sizeof(t) << std::endl;
}
int main()
{
const char t[34] = {'1'};
foo(t);
char d[34] = {'1'};
foo(d);
}
The output is
const char *
array ref
34
Why does the first foo calls the const char * version ? How can I make it call the reference version ?
Conversion of const char[N] to const char* is considered an "exact match" (to make literals easier, mainly), and between two exact matches a non-template function takes precedence.
You can use enable_if and is_array to force it to do what you want.
A messy way to force it might be:
#include <iostream>
template <typename T>
void foo(const T* c)
{
std::cout << "const T*" << std::endl;
}
template <typename T, size_t N>
void foo(const T (&t) [N])
{
std::cout << "array ref" << std::endl;
}
int main()
{
const char t[34] = {'1'};
foo(t);
char d[34] = {'1'};
foo(d);
}
/*
array ref
array ref
*/
I realise that the OP had char not some generic T, but nonetheless this demonstrates that the problem lay in one overload being a template and not the other.
Let's look at this modified example with no template.
void foo(const char * c)
{
std::cout << "const char *" << std::endl;
}
void foo(const char (&t) [34])
{
std::cout << "const char (&) [34]" << std::endl;
}
int main()
{
const char t[34] = {'1'};
foo(t);
}
My compiler says call of overloaded foo is ambiguous. This is because conversions from array to pointer are considered an "Exact" conversion sequence and are not better than the null conversion sequence for overload resolution (Standard section 13.3.3.1.1.)
In the original code, the template parameter N can be deduced as 34, but then both non-template foo(const char*) and foo<34>(const char (&)[34]) are considered in overload resolution. Since neither is better than the other by conversion rules, the non-template function beats the template function.
Fixing things seems tricky. It seems like the is_array template from header <type_traits> (from C++0x if possible or Boost if not) might help.
This appears to be different for various compilers.
Mircosoft and Borland both use the const char* version, while GNU is giving the output you described.
Here is a snippet from the C++ standard:
14.8.2.1 Deducing template arguments from a function call
[temp.deduct.call]
Template argument deduction is done by
comparing each function template
parameter type (call it P) with the
type of the corresponding argument of
the call (call it A) as described
below.
If P is not a reference type:
-- If A is an array type, the pointer type produced by the array-to-pointer
standard conversion (4.2) is used in
place of A for type deduction;
otherwise,
-- If A is a function type, the pointer type produced by the
function-to-pointer standard
conversion (4.3) is used in place of A
for type deduction; otherwise,
-- If A is a cv-qualified type, the top level cv-qualifiers of A's type
are ignored for type deduction.
If P is a cv-qualified type, the top
level cv-qualifiers of P's type are
ignored for type deduction. If P is a
reference type, the type referred to
by P is used for type deduction
The compiler will build an A list as follows:
Argument: t d
A: char const[34] char[34]
And parameter list P:
Parameter: c t
P: char const* char const& t[N]
By default the compiler should choose non-referenced parameters. GNU is dong it wrong the second time for some reason.