function templates overloading versus fully-specializing - c++

Hello I have this example:
#include <iostream>
template <typename T>
int compare(T const&, T const&);
template <typename T>
int compare(T*, T*);
template <>
int compare(char const * const&, char const* const&);
template <unsigned N, unsigned M>
int compare(char const (&)[N], char const(&)[M]);
template <unsigned N>
int compare(char const (&)[N], char const(&)[N]);
template <typename T>
int compare(T const& lhs, T const& rhs){
std::cout << "compare(T const&, T const&)\n";
if(std::less<T>()(lhs, rhs))
return -1;
if(std::less<T>()(rhs, lhs))
return 1;
return 0;
}
template <typename T>
int compare(T* p1, T* p2){
std::cout << "compare(T*, T*)\n";
if( std::less<T>()(*p1, *p2) )
return -1;
if( std::less<T>()(*p2, *p1) )
return 1;
return 0;
}
template <>
int compare(char const * const& p1, char const* const& p2){
std::cout << "compare(char const * const &, char const * const &)\n";
return strcmp(p1, p2);
}
template <unsigned N, unsigned M>
int compare(char const (&ra)[N], char const(&rb)[M]){
std::cout << "compare(char const(&)[N], char const(&)[M])\n";
return strcmp(ra, rb);
}
template <unsigned N>
int compare(char const (&ra)[N], char const(&rb)[N]){
std::cout << "compare(char const(&)[N], char const(&)[N])\n";
return strcmp(ra, rb);
}
int main(){
int a = 10, b = 57;
char const* const cp1 = "Hello";
char const* const cp2 = "World";
std::cout << compare(a, b) << '\n';
std::cout << compare(&a, &b) << '\n';
std::cout << compare(cp1, cp2) << '\n';
// std::cout << compare("Hi", "Hi") << '\n'; // error: ambiguous
// std::cout << compare("Hi", "World!") << '\n'; // error: ambiguous
cout << '\n';
}
Why the call to compare passing two arrays of of constant characters is ambiguous even the arrays of different lengths?
In fact the second overload that has a single non-type parameter N is just for compatibility with C++ 11 because on C++14 and above I can use std::enable_if. please
Why also passing compare(cp1, cp2); invokes compare(T*, T*) and not compare(char const* const&, char const * const&)?
One final question: Does my order of declaring the function templates here affects which function is preferred? Thank you.
Update:
I've manged to make it work by only changing the signature of the version taking two pointers to the parameter type: compare(T*, T*) to :
template <typename T>
int compare(T const * const&, T const* const&);
Now It works fine so can you explain why?

Question 1
Why the call to compare passing two arrays of of constant characters is ambiguous even the arrays of different lengths?
Answer 1
This is because when you write compare("Hi", "World!"); there are two version that can be used(equally good). First version is
template <typename T>
int compare(T*, T*);
And the second version is:
template <unsigned N, unsigned M>
int compare(char const (&ra)[N], char const(&rb)[M]);
Important Note
Note that the version:
template <>
int compare(char const * const& p1, char const* const& p2);
is a specialization and so it does not take part in overloading.To be more specific remember that:
Specializations instantiate a template; they do not overload it. As a result, specializations do not affect function matching.
Question 2
Why also passing compare(cp1, cp2); invokes compare(T*, T*) and not compare(char const* const&, char const * const&)?
Answer 2
This is because the version:
template <typename T>
int compare(T*, T*);
will be chosen for "all pointers" and more importantly it can take part in overloading while the version
template <>
int compare(char const * const& p1, char const* const& p2);
is a specialization and hence does not take part in overloading.
Question 3
Does my order of declaring the function templates here affects which function is preferred?
Answer 3
No

Why the call to compare passing two arrays of of constant characters is ambiguous even the arrays of different lengths?
Because both of
template <>
int compare(T*, T*);
template <unsigned N, unsigned M>
int compare(char const (&)[N], char const(&)[M]);
match in both cases, and in second case the third overload joins in as well (you might have specialised as template <unsigned N> compare<N, N>(...) to avoid ambiguity at least between those latter two.
Why also passing compare(cp1, cp2); invokes compare(T*, T*) and not compare(char const* const&, char const * const&)?
On selection of the right function template first overload resolution occurs and only when the right overload is selected template specialisations are considered. You now provide two template overloads of compare function:
template <typename T>
int compare(T const&, T const&);
template <typename T>
int compare(T*, T*);
Of these two, the pointer template is a better match, so this one is selected.
template <>
int compare(char const * const&, char const* const&);
is a specialisation of the overload that got discarded, though, and thus has been eliminated as candidate already before. 1)
By accepting pointers by value (char const* px) you'd have specialised the the other base template and thus would see the desired result.
Does my order of declaring the function templates here affects which function is preferred?
No. All overloads that are known at the time of calling the function are considered, no matter in which order they have been declared. If that wasn't the case it would be nearly impossible (at least only with significant research work) to predict which overload gets called if these are imported (possibly indirectly!) via different headers.
Edit (considering updated question):
template <typename T>
int compare(T const&, T const&);
template <typename T>
int compare(T const * const&, T const* const&);
template <>
int compare(char const * const&, char const* const&);
char const* still matches better T const* const& than T const&, so still second overload will be selected. If you now compare the involved signatures then your change provoked, though, the (actually unchanged) specialisation getting a specialisation of this second overload (char const* is closer to T const* const& than to T const&, just as was already during overload resolution).
As now the specialisation refers to the overload already having been selected, you now get the desired/expected result.
Side note: Leaving the original base template (T*) and changing the specialisation to
template <>
int compare(char const*, char const*);
would also have provoked the specialisation being a closer match to second overload – and this change would have been more conclusive as there's no reason for accepting a pointer by const reference – if not optimised away anyway, this is just yet another (needless) level of indirection (not so for non-const pointers, in which case you could apply a change to the pointer variable being passed to the function itself).
1)Adopted from Frank's answer which got deleted in the meanwhile.

Related

C++ primer 5th ed function template specialization

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;
}

template specialization taking a reference to const

I'm trying to understand how template specialziations work.
I've got the following function template:
template <typename T>
void function(const T &t1, const T &t2)
{
std::cout << "in function template: " << t1 << ", " << t2 << std::endl;
}
now, I want to specialize this function template in case it's called with a pointer to const:
// template specialization
template <>
void function(const char *&t1, const char *&t2)
{
std::cout << "in compare template specialization: " << t1 << ", " << t2 << std::endl;
}
but the compiler complains that it can't find a function template to specialize:
In file included from main.cpp:1:0:
template.h:23:5: error: template-id 'compare<>' for 'int compare(const char*&, const char*&)' does not match any template declaration
int compare(const char *&t1, const char *&t2)
^~~~~~~
template.h:10:5: note: candidate is: template<class T> int compare(const T&, const T&)
int compare(const T &t1, const T &t2)
if I specialize the template like this (a reference to a CONST pointer to const), it works:
// template specialization
template <>
int compare(const char * const &t1, const char * const &t2) // now the pointer itself is const
{
std::cout << "in compare template specialization: " << t1 << ", " << t2 << std::endl;
}
I want to call the function with a const char *Ptr = "hello world" so I thought the inferred parameter T was char* and the parameter was const char *&.
Aren't the const in the function template parameter list always low level const?
Templates are not a simple token replacement mechanism like macros. const T here doesn't mean "paste me whatever T is into the spot right after the const". It means that the type of the thing there is "const whatever T is". In the case of your function template, if you set T to be const char* then the type const T& will be a reference to a const whatever T is, i.e., a reference to a const char* that is itself const, i.e., a const char * const &. It's really no different than if T was a typedef'ed name rather than a template parameter, e.g.:
using T = int*;
const T blub = 42; // type of blub is int* const, not const int*
Therefore,
template <>
void function(const char*& t1, const char*& t2);
is not a valid specialization of the function template function. There is no T that you could substitute into your template function to get this signature. If you substitute const char* for parameter T, i.e., form function<const char*>, its signature will come out to be
void function<const char*>(const char * const& t1, const char * const& t2);
Note that, rather than rely on explicit specialization, if you want a separate function to treat the
void function(const char*& t1, const char*& t2);
case, simply add such a function and rely on overloading to work its magic. In general, when you find yourself writing explicit function template specializations, odds are that what you actually wanted to do is probably just use overloading. See also Template Specialization VS Function Overloading or this article (old, but still as true as ever) for more on that…

Template specialization unmatched error

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");
}

Can't correctly instantiate template when using C++ functional library

I'm trying to create a class of fixed size vectors, to use mostly for geometrical purposes,
where vector length doesn't change:
template<typename T, int n>
class FixedVector
{
private:
T m_rV[n]; // this is the only data member
public:
// class function members
...
}
This would have the advantage of compiler checking for operations with vectors of incompatible size.
I'm having problems when trying to build an operator* for this class (note: it is not a member). This operator should multiply the vector by a scalar, like this 3*[1,2,3]=[3,6,9].
template<typename T, int n>
FixedVector<T,n> operator*(const T &rX, const FixedVector<T,n> &cV) const
{ typename std::pointer_to_binary_function<T,T,T> op=(util::times<T>);
FixedVector<T,n> cT(cV, std::bind1st(op, rX));
return cT;
}
where times is the multiplication function of a scalar member of the vector
template<typename T>
inline T times(const T &t1, const T &t2)
{ return t1*t2;
}
the code for the constructor in line 4 is
template<typename T, int n>
FixedVector<T,n>::FixedVector(const T rV[n], T (*f)(const T &))
{ util::copy(m_rV, rV, n, f);
}
and pointer_to_binary_function and bind1st are STL functions from the header (Those who can help should know this already).
I'm getting the following compiler error in Visual Studio 2005 when calling
util::FixedVector<int,4> x; 3*x;
:
fixed_vector.hpp(212) : error 2440:
'initializing' : cannot convert from 'T (__cdecl *)(const T &,const T &)'
to 'std::pointer_to_binary_function<_Arg1,_Arg2,_Result>'
with
[
_Arg1=int,
_Arg2=int,
_Result=int
]
No constructor could take the source type, or constructor overload resolution was ambiguous
testproject.cpp(18) : see reference to function template instantiation 'util::FixedVector<T,n> util::operator *<T,4>(const T &,const util::FixedVector<T,n> &)' being compiled
with
[
T=int,
n=4
]
It appears that
typename std::pointer_to_binary_function
is correctly instantiated to
std::pointer_to_binary_function
However, times still remains to its basic signature:
'T (__cdecl *)(const T &,const T &)
--- AFTER SOME EDITING ----------------------------------------------------------------
It was indicated to me that a function that my constructor requests a plain function as parameter: T (*)(const T &t1, const T &t2) and that STL functional objects won't be accepted.
The link here STL for_each served as a guide on how to do the corrections.
I started to changing from the util::copy function, called by constructor.
From:
template
void copy(T *dst, const T *src, size_t n, T (*f)(const T &))
{ for (; n>0; n--, dst++, src++)
{ *dst = f(*src);
} }
it became
template<typename T, typename Function>
void copy(T *dst, const T *src, size_t n, Function f)
{ for (; n>0; n--, dst++, src++)
{ *dst = (*f)(*src);
} }
Then, the constructor itself was templated. From:
template<typename T, int n>
FixedVector<T,n>::FixedVector(const T rV[n], T (*f)(const T &))
{ util::copy(m_rV, rV, n, f);
}
it is now
template<typename T, int n>
template<class Function>
FixedVector<T,n>::FixedVector(const FixedVector<T,n> &cV, Function f)
{ util::copy(m_rV, cV.m_rV, n, f);
}
Also added some consts to template instantiation parameters:
template<typename T, int n>
FixedVector<T,n> operator*(const T &rX, const FixedVector<T,n> &cV)
{ typename std::pointer_to_binary_function<const T,const T,T> op(times<T>);
FixedVector<T,n> cT(cV, std::bind1st(op, rX));
return cT;
}
But I still get the same error (only that T has been replaced by const T; note that adding a & to indicate reference (const T&) will trigger an error and it seems that templates have problems with this, Boost and TR1 creating special solutions to deal with this - see Wikipedia TR1 reference wrapper).
The exact line of the error is this:
typename std::pointer_to_binary_function<const T,const T,T> op(times<T>);
So I don't even reach the constructor.
I'd be very grateful with some extra ideas.
Your function util::times has the signature:
T times(const T&, const T&)
but in this line:
typename std::pointer_to_binary_function<T,T,T> op=(util::times<T>);
the constructor of std::pointer_to_binary_function is expecting:
T times(T, T)
You can fix that by using const T& as argument template parameters:
typename std::pointer_to_binary_function<const T&, const T&, T> op(util::times<T>);
Note that I removed the = to use explicit constructor notation here. At least in (my) GCC, the assignment syntax is rejected by the compiler.
Since you are creating a binary functor that does multiplication, instead of the custom times function and functor wrapper, you could directly use std::multiplies to achieve the same:
std::multiplies<T> op;
Note that in your next line,
FixedVector<T,n> cT(cV, std::bind1st(op, rX));
you are calling a constructor that takes a FixedVector<T, n> as first argument, and a unary functor as second. This is not compatible with the constructor code you posted:
template<typename T, int n>
FixedVector<T,n>::FixedVector(const T rV[n], T (*f)(const T &))
{ util::copy(m_rV, rV, n, f);
}
since this one takes a plain array and a function pointer.
template<typename T, int n>
FixedVector<T,n>::FixedVector(const T rV[n], T (*f)(const T &))
{ util::copy(m_rV, rV, n, f);
}
You cannot convert to a function pointer from any functional object with state, and even then, only stateless C++0x lambdas. You must take a template functional object, or a polymorphic one like function, found in Boost, TR1 and C++0x.
Besides, this is incredibly obscure. Why not just do a regular multiply? It would be far simpler than this approach.

Template Functions and Const/NonConst Reference Parameters

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.