New to C++ and learning from books so I can be quite pedantic or miopic in my reasoning.
In the case of template functions, I have read that when a parameter is passed by Reference, only conversions from Reference / Pointer to NonConst to Reference / Pointer to Const are alllowed.
That means I believe that
template <typename T> int compare(T&, T&);
should fail when calling compare(ci1, ci1), with ci1 being consntant int, as conversions from Const to NonCost are not allowed for Reference parameters.
However it works in my compiler (Visual C++ 10). Can someone explain me what I get wrong?
template <typename T> int compare(T&, T&);
template <typename T> int compare(T &v1, T &v2)
{
// as before
cout << "compare(T, T)" << endl;
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
const int ci1 = 10;
const int ci2 = 20;
int i1 = 10;
int i2 = 20;
compare(ci1, ci1);
compare(i1, i1);
The call
compare( ci1, ci1 );
yields T as type const int (in your preferred notation).
The effective function signature is then
int compare( int const&, int const& )
You can use typeid(x).name() the check out what types you actually have.
Note: with g++ that yields some ungrokkable short forms, which you then need to use a special g++-specific runtime lib function to decode.
Cheers & hth.
T will be whatever type the variable is - in your case const int, so the final instantiation of compare will look like
// T = const int
int compare(const int& v1, const int& v2)
in your first case with compare(ci1,ci2) and like
// T = int
int compare(int& v1, int& v2)
with compare(i1,i2).
In the first case, the template is instantiated with T = const int, which is fine.
You will get an error if you try compare(i1, ci1), since that will fail to find a template argument compatible with both int & and const int &. Changing the signature to compare(const T &, const T &) will fix that problem.
In the case of compare(ci1, ci1); T will be const int. This is why it works
This is acceptable because the function can be instantiated when you substitute int const for T.
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;
}
// case 1
const int i = 42;
const auto &k = i;
// case 2
const int i = 42;
auto &k = i;
Do we need the const keyword before auto in this scenario? After all, a reference (k) to an auto-deduced type will include the top level const of the object (const int i). So I believe k will be a reference to an integer that is constant (const int &k) in both cases.
If that is true, does that mean that const auto &k = i; in case 1 is replaced by the compiler as just const int &k = i; (auto being replaced with int)? Whereas in case 2, auto is replaced with const int?
auto keyword automatically decides the type of the variable at compile time.
In your first case, auto is reduced to int where it's reduced to const int in the second case. So, both of your cases are reduced to the same code as:
const int &k = i;
However, it's better to have the const explicitly for better readability and to make sure your variable TRULY is const.
Type deduction with auto works like template argument type deduction with a few exceptions that don't apply in the given example. Hence
const int i = 42;
const auto& k1 = i; // same as const int& k1 = i;
auto& k2 = i; // same as (const int)& k2 = i;
It is probably more readable to add the const qualifier nevertheless.
Here is another example where favoring brevity with auto is misleading:
int *i;
const auto k1 = i; // same as int* const k1 = i;
const auto *k2 = i; // same as const int *k2 = i;
In the first case, the object that i points to can be modified through k1, in the second case, it can't.
Hi and welcome to stack overflow.
As this little test program shows, no matter how you specify the type of k, the compiler will never let you lose the constness of i.
#include <iostream>
#include <type_traits>
#include <string>
#define XSTR(s) STR(s)
#define STR(s) #s
template<class T>
struct type;
template<>
struct type<int>
{
static std::string name() { return "int"; }
};
template<class T>
struct type<T&&>
{
static std::string name() { return type<T>::name() + " &&"; }
};
template<class T>
struct type<T&>
{
static std::string name() { return type<T>::name() + " &"; }
};
template<class T>
struct type<T const>
{
static std::string name() { return type<T>::name() + " const"; }
};
#define REPORT_CONST(decl, var, assign) \
{ \
decl var = assign; \
do_it(STR(decl var = assign;), var); \
}
template<class Var>
void do_it(const char* message, Var&&)
{
std::cout << "case: " << message << " results in type: " << type<Var>::name() << '\n';
}
int main()
{
const int i = 42;
REPORT_CONST(const auto &, k, i);
REPORT_CONST(auto &, k, i);
REPORT_CONST(auto &&, k, std::move(i));
REPORT_CONST(auto const &&, k, std::move(i));
REPORT_CONST(int const&, k, i);
// REPORT_CONST(int &, k, i); // error: binding reference of type 'int&' to 'const int' discards qualifiers
}
Expected results:
case: const auto & k = i; results in type: int const &
case: auto & k = i; results in type: int const &
case: auto && k = std::move(i); results in type: int const &
case: auto const && k = std::move(i); results in type: int const &
case: int const& k = i; results in type: int const &
http://coliru.stacked-crooked.com/a/7c72c8ebcf42c351
Note also the decay of named r-values to l-values.
There is a slight difference in first case auto will be deduced to const int and in the second case to int (as you explcitly stated the const).
cpp-reference states
The keyword auto may be accompanied by modifiers, such as const or &, which will participate in the type deduction. For example, given const auto& i = expr;, the type of i is exactly the type of the argument u in an imaginary template template void f(const U& u) if the function call f(expr) was compiled. Therefore, auto&& may be deduced either as an lvalue reference or rvalue reference according to the initializer, which is used in range-based for loop.
So for you this means
// case 1
const int i = 42;
const auto &k = i; // auto -> int
// case 2
const int i = 42;
auto &k = i; // auto -> const int
More important in my opinion is, however, that the intent is stated more clearly if you state const explcitly and the guarantees the constness. Therefore in this case I would clearly prefer it.
The accepted answer is correct, i.e. there is no difference in regards to the compiled result. What's important to note is that the auto& version is coupling the const-ness of the k reference with the const-ness of the variable i. I figured since the question is titled 'Difference between const auto & and auto & ...' then it's important to emphasize the pragmatic difference here, in which if you don't put a the const keyword, you cannot guarantee the reference will have this cv-qualification. In some scenarios where this is desired, why leave it to chance that i will remain const in the future?
I just got a compiler error for the following construct:
size_t N = 10;
size_t Ntransforms = std::min(PySequence_Fast_GET_SIZE(__transforms), N);
since PySequence_Fast_GET_SIZE() actually returns Py_ssize_t and std::min() is defined as
template <class T> const T& min (const T& a, const T& b);
Since it takes lvalue references of the same type, I was surprised that I could fix it with an inline typecast:
size_t Ntransforms = std::min((size_t)PySequence_Fast_GET_SIZE(__transforms), N);
Is this OK? If yes, is it equivalent to the explicit code:
size_t Ntransforms = PySequence_Fast_GET_SIZE(__transforms);
if (Ntransforms > N) Ntransforms = N;
The std::min signature forces the same type of arguments:
template <class T> const T& min (const T& a, const T& b);
The call is ambiguous in case the types of arguments differ.
However, it is fine to use temporaries (resulting e.g. from a cast expression) in place of arguments, because std::min takes const lvalue references.
Alternatively, you can force the type used to instantiate std::min:
std::min<std::size_t>(PySequence_Fast_GET_SIZE(__transforms), N);
Yes, you can. Yet it is equivalent to the variant you specified. You are free to use the form:
size_t Ntransforms = std::min(static_cast<size_t>(PySequence_Fast_GET_SIZE(__transforms)), N);
What I'm trying to do:
Write a specialized version of the template from the previous exercise to handle vector<const char*> and a program that
uses this specialization.
I wrote the program like this:
template<typename T>
int count(vector<T> tvec, const T &t);
template<>
int count(vector<const char *> tvec, const char *const &s)
{
int count = 0;
for (auto c : tvec)
if (c == s) {
++count;
}
return count;
}
template<typename T>
int count(vector<T> tvec, const T &t)
{
int count = 0;
for (auto c : tvec)
if (c == t) {
++count;
}
return count;
}
cout << count(svec, "GUO");
but I get the error that says
deduced conflicting types for parameter ‘const T’ (‘std::basic_string<char>’ and ‘char [4]’)
I want to know how to handle this. and further, in the template function, it seems that an array can be changed to the pointer, why my program cannot handle it?
Don't deduce on both parameters, it leads to conflicts. Write this:
template <typename T>
int count(const vector<T>& tvec, const typename vector<T>::value_type& t);
Also, consider overloading instead of specializing. Specializing a function template is pretty much never what you want.
Firstly, it seems svec is defined as vector<string>, maybe it should be vector<const char*>;
Secondly, explictly define a var as const char*;
Try this:
vector<const char*> svec;
const char* chars = "GUO";
std::cout<<my_count(svec,chars);
BTW: A variable of type char array(char[]) can be used as type char pointer(char*), but they are different as type, and they are different as a template paremeter.
Consider something like:
template <typename T>
void f(T& x)
{
....
}
Why does something like const int binds to f(T&)?
This seems to me kind of a violation of const-correctness. In fact, if f() takes a non-const T& reference, then it's very likely that f() will modify its argument (else, f() would have been defined as void f(const T&)).
In code like this:
template <typename T>
inline void f(T& x)
{
x = 0;
}
int main()
{
int n = 2;
f(n);
const int cn = 10;
f(cn);
}
the compiler tries to call f() with T = const int, then of course there is an error message because of the x = 0; assignment inside f()'s body.
This is the error message from GCC:
test.cpp: In instantiation of 'void f(T&) [with T = const int]':
test.cpp:13:9: required from here
test.cpp:4:7: error: assignment of read-only reference 'x'
x = 0;
^
But why does the compiler try to bind a const argument with a function template which takes a non-const parameter?
What's the rationale behind this C++ template rule?
T binds to const int.
To avoid that, you may use SFINAE:
template<typename T>
typename std::enable_if<!std::is_const<T>::value, void>::type
f(T& arg) {}
or deleted function:
template <typename T> void f(T& arg) {}
template <typename T> void f(const T&) = delete;
You can use std::enable_if plus e.g. std::is_const to avoid that T binds to a const type.
Re …
“What's the rationale behind this C++ template rule?”
it can possibly be found in Bjarne's design-and-evolution book, but about the most common rationale is that the rules have been chosen for simplicity and uniformity, and so it appears to be also here: treating some types in special ways would introduce needless complexity.
const int cn = 10;
That means 'cn' is const, you can't change it in anyway, anywhere and anytime.
More:
const int cia = 10;
int ia = 10;
The type of cia is different with ia. So T will be const int, not int.
typedef const int cint;
cint cia = 10;
int ia = 10;
T will be used as cint, not int.