I am confused by how C++ instantiate template. I have a piece of code:
template <class T, int arraySize>
void test1(T (&array)[arraySize])
{
cout << typeid(T).name() << endl;
}
template<class T>
void test2(T &array)
{
cout << typeid(T).name() << endl;
}
int main()
{
int abc[5];
test1(abc);
test2(abc);
return 0;
}
Here are my questions:
1. How does the size of array abc is passed to test1 (the parameter arraySize )?
2. How does C++ compiler determine the type of T in the two templates?
There is no parameter passing in the normal sense, since template parameters are resolved at compile time.
Both arraySize and T are inferred from the type of the array parameter. Since you pass an int[5], arraySize and T become 5 and int, respectively, at compile time.
If, for example, you declared int* abc = new int[5];, your compiler would barf at the point you try to call test1(abc). Apart from a basic type-mismatch, int* doesn't carry enough information to infer the size of the array.
It is called template argument deduction.
The type of abc at call-site is : int(&)[5] which has two info combined: int and 5. And the function template accepts argument of type T(&)[N], but the argument at call-site is, int(&)[5], so the compiler deduces that T is int and N is 5.
Read these:
The C++ Template Argument Deduction (at ACCU)
Template argument deduction (C++ only) (at IBM)
In test1 the compiler creates a template with T[arraySize] being its form.
When you call test1(abc) you are providing an input argument of type int[5] which the template matcher automatically matches.
However, if you were to write
int n=10;
int *abc = new int[n];
test1(abc);
test1<int,n>(abc);
then the compilation would fail and the compiler would claim that it has no template matching the test1(abc) function call or the test1< int,n >(abc) function call.
This is because the size of abc is now dynamically allocated and so the type of abc is a pointer which has a different type and hence no template could be matched to the above two calls.
The following code shows you some types
#include <iostream>
using namespace std;
template <class T> void printName() {cout<<typeid(T).name()<<endl;}
int main()
{
printName<int[2]>(); //type = A2_i
printName<int*>(); //type = Pi
getchar();
return 0;
}
I'd also add to what Nawaz says: the theory is type inference.
Related
I am leaning the topic templates in C++, it says I can also assign the datatype in the template syntax. But if I pass a different datatype in the object of class and call the method of my class it should throw an error or a garbage output, but it does not whereas it gives the correct output if I assign the datatype while declaring the object of the class and calling it. Why is so?
Here is the program I was practicing on
#include <iostream>
using namespace std;
template <class t1 = int, class t2 = int>
class Abhi
{
public:
t1 a;
t2 b;
Abhi(t1 x, t2 y)
{
a = x;
b = y;**your text**
}
void display()
{
cout << "the value of a is " << a << endl;
cout << "the value of b is " << b << endl;
}
};
int main()
{
Abhi A(5,'a');
A.display();
Abhi <float,int>N(5.3,8.88);
N.display();
return 0;
}
I am facing the issue in the first object A while the second object N gives the correct output
The output of the above program for the first object is
the value of a is 5
the value of b is a
The output of the above program for the second object is
the value of a is 5.3
the value of b is 8
A char can be implicitly converted to an int. Although the type of A will be deduced by C++20 to Abhi<int,char>. That's why when you output it you get an a and not its corresponding integer representation.
See CTAD for a more detailed explanation of the mechanism.
More interesting is why your compiler implicitly converts double to int or float in N. This indicates that your compiler warning flags are insufficiently high or you are actively ignoring them.
When you write Abhi A(5,'a');, it doesn't use the default arguments of the Abhi template, instead the template type arguments are deduced from the constructor arguments values, in this case A is an Abhi<int, char>. This is a process called Class Template Argument Deduction.
You would get the default arguments if you wrote Abhi<> A(5, 'a');.
When you write Abhi <float,int>N(5.3,8.88); you have specified the types, but C++ allows conversion between arithmetic types. Unfortunately that includes discarding the fractional part when converting a decimal to int. Often there are settings for your compiler to warn of or forbid these lossy conversions.
The conversion from char to int isn't problematic, because there is an int value for every char value.
We can see that all here
I have this example from C++ primer 5th ed:
template <typename T>
int compare(T const& x, T const& y) // in the book const T&
{
cout << "primary template\n"; // I've added print statements
if(std::less<T>()(x, y))
return -1;
if(std::less<T>()(y, x))
return 1;
return 0;
}
template <unsigned N, unsigned M>
int compare(char const(&rArr1)[N], char const(&rArr2)[M])
{
cout << "overload for arrays\n";
return strcmp(rArr1, rArr2);
}
template <>
int compare(char const* const &p1, char const* const &p2)
{
cout << "specilization for char* const\n";
return strcmp(p1, p2);
}
int main()
{
std::cout << compare("Hi", "Mom") << '\n';
}
"Whether we define a particular function as a specialization or as an independent, non-template function can impact function matching. 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."
I think the it is incorrect: "compare("hi", "mom"); both templates are viable" because as I see the first version that takes T const&, T const& is not viable because it is instantiated as:
compare(char const(&rArra1)[3], char const(&rArr2)[4]);
In which case sizes are different and as we know the size of an array is a part of its type thus here the two literal strings have two different types thus the Template Argument Deduction will fail thus this instantiation is rejected. it is as if we wrote: compare(5, 0.); // int, double so the compiler cannot deduce a unique type as it is required here.
To confirm what I am saying if I comment out the version taking array types then the code won't compile for that call with two literal strings with different sizes.
What confuses me too is that if I comment out the arrays-type version the code doesn't compile even if I have the third version (taking pointer to character strings)! Is this because of the fact that a Specialization doesn't affect function matching? and what was rejected by a primary template is rejected by all of its specializations?
Please clarify. Thank you!
I know this question probably has been asked before but I'm a beginner to templates, and here is my code,
HeaderFile.h
class Identity {
public:
template <typename T>
T getFamilyMembers() {
if(1) {
return false;
}
std::string whatever = "Test";
return whatever;
}
};
Main.cpp
#include "HeaderFile.h"
int main() {
Identity id;
std::cout << id.getFamilyMembers() << "\n";
}
Compiler issue two errors,
Main.cpp:25:10: error: no matching member function for call to 'getFamilyMembers'
id.getFamilyMembers();
~~~^~~~~~~~~~
HeaderFile.h:22:11: note: candidate template ignored: couldn't infer template argument 'T'
T getFamilyMembers() {
This isn't how templates work.
The caller specifies what T is. When T is a return type, then all return statements in the function must be a T value, or something implicitly convertible to T. The function itself can't dictate what T is at runtime.
Beyond that, T is known at compile time. You can't return different types in different situations without the help of a variant type like std::variant.
For each distinct T, a totally new version of the function is created. Template functions, therefore, are used when a generic algorithm can be applied to multiple types. For example, a naive implementation of a "swap two values" algorithm might copy one of the values to a temporary variable:
void swap(int & a, int & b) {
int temp = a;
a = b;
b = temp;
}
But now we have to write a new swap() function for each possible type of thing we ever want to swap! Enter templates: we write the function once, as a template:
template <typename T>
void swap(T & a, T & b) {
T temp = a;
a = b;
b = temp;
}
Now the compiler will do the work for us, creating new versions of swap() whenever it sees a T that it hasn't before.
In your particular case, if you want to return a value that might be a string but might be nothing, that's what std::optional is for. In this case, you could return an std::optional<std::string>. You could also just return std::string and use an empty string to notate that there are no family members.
Given that the name of the function implies that multiple items could be returned, it might make more sense to have the function return std::vector<std::string> and then the "no family members" case is simply represented as an empty vector.
I am a bit puzzled with the following behavior. I pass a function with two parameter, one having a default value, as a template parameter and call the function with one argument. Why does it fail to compile? And, what is the solution/workaround?
#include <iostream>
using namespace std;
template<typename Function>
void eval(Function function) {
function(10);
}
void sum(int i, int j = 0) {
cout << "Sum is " << i + j;
}
int main() {
sum(10); // OK, of course :)
eval(sum); // Error!
}
Note that this question is not about calling a templated function with default parameter.
The error message:
prog.cpp: In instantiation of 'void eval(Function) [with Function = void (*)(int, int)]':
prog.cpp:15:10: required from here
prog.cpp:6:10: error: too few arguments to function
function(10);
^
That's because the optional parameter is part of function declaration. When you call using a function pointer, essentially all the compiler knows is the type of the function pointer. So this line function(10) roughly translates to:
void (*)(int, int) sm = function; // the equivalent happens at the type deduction step
sm(10); // whoops, where is the second int
The compiler will need the second argument because it has no way of knowing whether sm is pointing to sum which has a default argument, or some other void foo(int a, int b) which doesn't have a default argument.
This is why function pointers are weak. You got two good answers of why this doesn't work - the function template doesn't know your function has a defaulted argument.
The solution is to not pass a function pointer - pass in a function object. In this case, you can handwrite a lambda:
eval([](int i){ return sum(i); });
which seems like a really annoyingly verbose way of basically just writing sum in a way that actually works. But this sort of thing comes up periodically (what if sum were an overloaded name?) so it's handy to just have a lambdafying macro around (C++14 required):
#define AS_LAMBDA(func) [&](auto&&... args) -> decltype(auto) { \
return func(std::forward<decltype(args)>(args)...); }
so you can write:
eval(AS_LAMBDA(sum));
That will work with defaulted arguments, overloaded names, and even work to bind member functions:
struct X {
int i;
int add(int j) { return i + j; }
};
X x{42};
eval(AS_LAMBDA(x.add));
When you call sum:
sum(10);
compiler translates it to:
sum(10, 0);
There is no error, as arguments are matched. For the compiler there exist no sum taking one argument. You can say a pre-compiler doing the magic of passing 0 to second argument to sum. With templates this magical pre-compiler doesn't exist, and the actual compiler matches sum with two strictly two arguments, which it doesn't find, and hence the error.
Just like
void foo(int);
void foo(float);
will fail to compile with foo(10.2); where argument double can be changed to either float or int. The compiler will not assume float for you. Similarly, compiler will not try to find best match of sum just for the sake of successful compilation.
Please consider this code:
#include <iostream>
template<typename T>
void f(T x) {
std::cout << sizeof(T) << '\n';
}
int main()
{
int array[27];
f(array);
f<decltype(array)>(array);
}
Editor's Note: the original code used typeof(array), however that is a GCC extension.
This will print
8 (or 4)
108
In the first case, the array obviously decays to a pointer and T becomes int*. In the second case, T is forced to int[27].
Is the order of decay/substitution implementation defined? Is there a more elegant way to force the type to int[27]? Besides using std::vector?
Use the reference type for the parameter
template<typename T> void f(const T& x)
{
std::cout << sizeof(T);
}
in which case the array type will not decay.
Similarly, you can also prevent decay in your original version of f if you explicitly specify the template agument T as a reference-to-array type
f<int (&)[27]>(array);
In your original code sample, forcing the argument T to have the array type (i.e. non-reference array type, by using typeof or by specifying the type explicitly), will not prevent array type decay. While T itself will stand for array type (as you observed), the parameter x will still be declared as a pointer and sizeof x will still evaluate to pointer size.
The behaviour of this code is explained by C++14 [temp.deduct.call]:
Deducing template arguments from a function 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
and then 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;
For the call f(array);, we have A = int[27]. A is an array type. So the deduced type T is int *, according to this last bullet point.
We can see from the qualifier "If P is not a reference type" that this behaviour could perhaps be avoided by making P a reference type. For the code:
template<typename T, size_t N>
void f(T (&x)[N])
the symbol P means T(&)[N], which is a reference type; and it turns out that there are no conversions applied here. T is deduced to int, with the type of x being int(&)[N].
Note that this only applies to function templates where the type is deduced from the argument. The behaviour is covered by separate parts of the specification for explicitly-provided function template parameters, and class templates.
You can also use templates like the following:
template <typename T, std::size_t N>
inline std::size_t number_of_elements(T (&ary)[N]) {
return N;
}
This little trick will cause compile errors if the function is used on a non-array type.
Depending on your use case, you can work around that using references:
template<typename T>
void f(const T& x) {
std::cout << sizeof(T);
}
char a[27];
f(a);
That prints 27, as desired.