Template Argument Deduction from String Literal - c++

I am currently thinking about how to best constrain a generic type of a template to an std::sting as well as string literals. Therefore I compare the deduced type with the desired type using std::is_same. In case of an std::string this works right away. For a string literal, meaning a char const array, it only works if I use std::decay on the type and then compare the result to the type char const *. If I directly compare the deduced type to what I think it should be, is_same returns false, as is illustrated by the following example code.
template <class TYPE>
void function(TYPE&& parameter)
{
//this doesn't work as expected
std::cout << typeid(TYPE).name() << " : " << typeid(char const [5]).name() << std::endl;
std::cout << std::is_same<char const [5], TYPE>::value << std::endl;
//this works as expected
std::cout << typeid(std::decay_t<TYPE>).name() << " : " << typeid(char const *).name() << std::endl;
std::cout << std::is_same<char const *, std::decay_t<TYPE>>::value << std::endl;
}
int main(int argc, char** argv)
{
function("name");
return 0;
}
The output generated is the following:
char const [5] : char const [5]
0
char const * __ptr64 : char const * __ptr64
1
Now, what I am wondering is why is_same returns false in the first case even though the types appear to be identical.
The only possible explanation that I could come up with is that within the function std::is_same a transformation similar to std::decay is applied to the type (for instance a function call). But then again this transformation would also occur to the other type, yielding the same result and thus resulting in equality.

String literals are not passed by value as char const [N], but by reference as char const (&)[N].
This works correctly for me:
std::cout << std::is_same<char const (&)[5], TYPE>::value << std::endl;
Note here that
1) Refers to a std::type_info object representing the type type. If type is a reference type, the result refers to a std::type_info object representing the referenced type.
You can easily verify that is_same doesn't discard reference-ness in the same way as type_info, for example by checking that
std::is_same<int&, int>::value == false
This explains why the typeid name is the same, but your is_same test still fails.

Using gcc custom function:
template < class T >
constexpr std::string type_name()
{
std::string p = __PRETTY_FUNCTION__;
return p.substr( 43 + 10, p.length() - 100 - 1 - 10 );
}
And adding it to your code:
std::cout << type_name<TYPE>() << " : " << type_name<char const [5]>() << std::endl;
The results are:
A5_c : A5_c
0
const char (&)[5] : const char [5]
So you need to use std::remove_reference on TYPE.

It may be an mistake of the compiler. The compiler generates such types-objects given by typeid at compiletime. The compiler wouldn't compile array-types for each length (0 to 2**n), so it compiles them when needed, and it may "forget" about the duplicate. Try to use a special template where you seperate the length from the contained type. This is not the type but you can check if it is equal to another.
template<class T, size_t size>
struct array_t{};
template <class T, size_t size>
void function(T[size] parameter) {
std::cout << typeid(array_t<T, size>).name() << " : " << typeid(array_t<char, 5>).name() << std::endl;
std::cout << std::is_same<array_t<T, size>, array_t<char, 5>>::value << std::endl;
};

Related

Accessing variable template using decltype

A minimized example of my code showing the problem:
#include <cassert>
#include <iostream>
#include <map>
#include <string>
template <typename T>
const std::map<std::string, T> smap;
template <>
const std::map<std::string, bool> smap<bool>{{"a", false}};
int main() {
std::map<bool, std::string> rmap{{false, "x"}};
for (const auto& [key, val] : rmap) {
std::cerr << typeid(bool).hash_code() << "\n";
std::cerr << typeid(decltype(key)).hash_code() << "\n";
std::cerr << smap<bool>.size() << "\n";
std::cerr << smap<decltype(key)>.size() << "\n";
assert((std::is_same_v<bool, decltype(key)>));
}
return 0;
}
Godbolt
It gives the output:
10838281452030117757
10838281452030117757
1
0
example.cpp:22: int main(): Assertion `(std::is_same_v<bool, decltype(key)>)' failed.
Why is it that I can't access the variable template using decltype when it's referring to the same type (bool)?
For the record I also tried to not use structured binding and using decltype on first in the pair with the same result.
However if I create an actual bool variable, like so ...
bool b;
std::cerr << settings_map<decltype(b)>.size() << "\n";
... it's working.
decltype(key) is const bool, not bool. And typeid strips const qualifiers, so the two have the same (runtime) representation.
If the type of type or expression is cv-qualified, the result of the typeid refers to a std::type_info object representing the cv-unqualified type (that is, typeid(const T) == typeid(T)).
So while typeid treats the two types as equivalent, template expansion (and is_same_v) does not, and you get two different maps: one for bool and one for const bool. Note that the assertion
assert((std::is_same_v<const bool, decltype(key)>));
succeeeds if put in place of the one in your code. To remove cv-qualifiers, use remove_cv_t.
std::cerr << settings_map<std::remove_cv_t<decltype(key)>>.size() << "\n";
In your last snippet
bool b;
std::cerr << settings_map<decltype(b)>.size() << "\n";
The b is not constant, so decltype(b) is actually bool, not const bool.
Keys are const so you'll need to remove it to make the assertion pass.
assert((std::is_same_v<bool, std::remove_cv_t<decltype(key)>>));
the value_type is std::pair<const Key, T>, so decltype(key) is const bool, not bool
and you capture it by const auto& so it's const anyway
typeid remove all cvref qualifiers, that's why you get same hash_code
as a side note, hash_code is a hash and no guaranteed on they would not collide, you can use operator== to check type equality.

why can not add low-level const type to local variable in template function

I'm studying c++ template, and write a example as shown below:
#include <vector>
#include <iostream>
#include <type_traits>
#include <typeinfo>
using namespace std;
template <typename T>
void fcn(T &&val)
{
cout << is_same<T, int&>::value << endl;
const T t = val;
//T const t = val;
cout << typeid(t).name() << endl;
t = 10;
cout << "val: " << val;
cout << ", t: " << t << endl;
return;
}
int main()
{
cout << boolalpha;
int i{};
fcn(i);
}
I expect this code can't compile successfully, but there are no error, the output:
true
i
val: 10, t: 10
I have two questions:
gcc deduce the T is int& type, the line const T t = val, means t const bind to val, like this: const int &t = val, why the const has no effect? t and i value can be changed?
I used typeid(t).name()to show the t's type, why just print i?
In your case, const T with T = int& means, give a reference which I can't reassign. But a reference is never assignable, it always points to the same object. So it collapses to T, which is in that case int&.
To make it works as expected, used std::remove_reference
See : const references in c++ templates
name returns something that is implementation-defined, so there is not much to say about it.
gcc deduce the T is int& type, const T t = val, means t const bind to val, why the const has no effect?
This function template signature
template <typename T> void fcn(T &&val);
uses a forwarding reference. In short, when deducing the instantiation with an lvalue int (your case), you end up with T = int&, i.e., including the reference. Reference collapsing transformes void fcn(T& &&val) to void fcn(T& val).
When you declare const T t, this means T& const (const modifies what's on its left, and if there is nothing on its left, it modifies what's on its right). But this isn't valid as there is no such thing as a const-qualified reference, hence the const-qualifier is dropped.
I used typeid(t).name() to show the t's type, why just print i?
typeid(t).name() isn't required to output a human readable identifier. i stands for int, so depending on the compiler, this is what you should expect. When working with clang or gcc, it is however often nicer to display a type through
template <class T> p(T&&) { std::cout << __PRETTY_FUNCTION__; }
p(t); // prints out understandable type info
I write another program to research const T which using "type alias":
#include <iostream>
using namespace std;
int main()
{
int i {};
using T = int *;
const T t = &i; // same with T const t = &i;
*t = 10;
cout << i << endl;
int j{};
//t = &j; // error, t is int * const
}
it compile successfully, output: 10.
now I can understand why compiler considers const T is int * const, but not const int *, because that compiler treats T as a common type like int, char, not int *, it is not substitution.

Why the type did not deduced to `const` type in both auto and template function cases? [duplicate]

This question already has answers here:
why auto i = same_const_variable could not deduce "const"?
(2 answers)
Closed 4 years ago.
This is my first question here and I hope I have added all relevant information to have a minimal verifiable complete program. In case you need more information kindly comment to me.
I am learning C++ template programming and type deduction.
case I: I used auto keyword for the type and cast the integer to a const double.
case II: I used a template function(type_fun) and told the compiler to do the deduction to const double by casting as follows:
#include <iostream>
#include <type_traits>
template<typename T>
void type_fun(T t)
{
std::cout << "From type function: " << std::endl;
std::cout << std::boolalpha << std::is_same_v<const double, decltype(t)> << std::endl;
std::cout << std::boolalpha << std::is_same_v<double, decltype(t)> << std::endl;
}
int main()
{
auto t = static_cast<const double>(1);
std::cout << std::boolalpha << std::is_same_v<const double, decltype(t)> << std::endl;
std::cout << std::boolalpha << std::is_same_v<double, decltype(t)> << std::endl;
type_fun(static_cast<const double>(1));
}
However, the results are the same. The program says the deduced type is a non-const double. Why the compiler neglected the const?
What do I need to do, to achieve the deduced type to be same as the static_casted type?
It may sound strange, but const is only for variables (and other things), but not for constants, since constants are already always const.
In your code:
auto t = static_cast<const double>(1);
t will get the type double and this explains the behavior you see.
What do I need to do, to achieve the deduced type to be same as the static_casted type?
You cannot differentiate a const double and double when this is passed as actual parameter to a function. Both are passed by value and both are 100% const (also without const) from the callers point of view. The function cannot modify the value of the caller. So the compiler makes no effort to differentiate between them.
If you want the const double variant to be printed in the template function you have to make it:
template<typename T>
void type_fun(const T t)
{
std::cout << "From type function: " << std::endl;
std::cout << std::boolalpha << std::is_same_v<const double, decltype(t)> << std::endl;
std::cout << std::boolalpha << std::is_same_v<double, decltype(t)> << std::endl;
}
But this variant will then always print the const double variant, regardless of the type which was passed in. The const near a formal parameter means in this case that the value which was passed by value (non-const or const) cannot be changed within the function.
const is used in too many contexts for too many purposes. It can be confusing.

Rationale for overloading C-String and char pointers [duplicate]

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.

What is the exact type of "" when deduced by `auto`?

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;