#include <iostream>
#include <array>
#define print(x) std::cout << x
#define println(x) std::cout << x << std::endl
template<std::size_t Size>
void Print(std::array<int, Size>& arr) {
for (int i = 0; i < Size; i++) {
println(arr[i]);
}
}
int main() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
Print(arr);
}
How does the size got passed to the function template without defining it like Print<5>(arr) ? (at line 7 "the actual template", at line 16 "calling the function")
How does the size got passed to the function template without defining it like Print<5>(arr) ?
It is thanks to template argument deduction. The size is deduced from the call Print(arr). Print() is a function template with a non-type template parameter (i.e., Size) of type std::size_t:
template<std::size_t Size>
void Print(std::array<int, Size>&);
That is, the template parameter Size is deduced to a value of type std::size_t, which corresponds to the second template argument of the std::array passed as a function argument when calling Print().
That’s called “template argument deduction” where it can figure it out based on what you’re passing in.
https://en.cppreference.com/w/cpp/language/template_argument_deduction
Related
In this code:
#include <cstddef>
#include <array>
#include <iostream>
// here
template <typename T, std::size_t size>
void printArray(const std::array<T, size>& myArray) {
for (auto element : myArray)
std::cout << element << ' ';
std::cout << '\n';
}
int main() {
std::array myArray1{ 9.0, 7.2, 5.4, 3.6, 1.8 };
printArray(myArray1);
// shouldn't this line be
printArray<double, 5>(myArray1)
return 0;
}
I understand how the template and function works, but what I don't understand is where std::size_t is being passed in on line 16. I know templates will deduce the type. Will it automatically pass in the array size, too?
I know templates will deduce the type. Will it automatically pass in the array size, too?
Yes, the value of the template non-type parameter size will be deduced from the type of the function argument, same as the template type parameter T is deduced.
Simple example first, doesn't compile with GCC12:
#include <array>
#include <iostream>
#include <initializer_list>
template <int nvars> class Test1 {
public:
Test1( std::initializer_list<int> IL ) : arr { 0 }
{
// See https://stackoverflow.com/questions/38932089/can-i-initialize-an-array-using-the-stdinitializer-list-instead-of-brace-enclo
};
const std::array<int,nvars> arr;
};
// CTAD guide / simple-template-id
Test1(std::initializer_list<int> IL) -> Test1<IL.size()>;
int main() {
Test1 test1({1,2,3,4});
std::cout << "nvars: " << test1.arr.size() << std::endl;
}
The simple-template_id here is Test1<IL.size()>, where IL.size() is a constexpr function returning 4. It does compile with a hardcoded 4, but that obviously defeats the purpose.
What is and is not allowed in the simple-template_id of the CTAD guide? Which names, which types of expressions?
The problem is that parameters like IL are not constexpr. This means that we can't use IL.size() as a template argument since template arguments are required to be compile time constant.
Thus to solve this we have to pass a compile time constant as a template argument. One way of doing this would be to make the parameter as a reference to an array of size N with elements of type T where both T and N are template parameters.
template<typename T, std::size_t N> Test1(T const(&&)[N]) -> Test1<N>;
I have the following templated code
#include <vector>
#include <array>
#include <iostream>
template<typename T1>
void foo(std::vector<T1> bar) {
std::cout << "GENERIC" << std::endl;
}
template<typename T1>
void foo(std::vector<std::vector<T1>> bar) {
std::cout << "SPECIFIC (vector)" << std::endl;
}
template<typename T1, int SIZE>
void foo(std::vector<std::array<T1, SIZE>> bar) {
std::cout << "SPECIFIC (array)" << std::endl;
}
int main() {
std::vector<std::vector<int>> a(2, std::vector<int> { 1, 2, 3});
std::vector<std::array<int, 3>> b(2, std::array<int, 3> {4, 5, 6});
foo(a);
foo(b);
}
which produces
SPECIFIC (vector)
GENERIC
I'm wondering why the vector-of-vector version is called with the specific template, but the vector-of-array version is called with the generic?
template<typename T1, size_t SIZE>
void foo(std::vector<std::array<T1, SIZE>> bar) {
std::cout << "SPECIFIC (array)" << std::endl;
}
You should use std::size_t instead of int.
run here
Edit :
Actually, your comments and my intuition about the code led me to dig into the topic. At first glance, a standard developer ( like me ) expect compiler to convert int to std::size_t(because they are both integral type and implicitly converting is very trivial) and select void foo(std::vector<std::array<T1, SIZE>> bar) as best specialization. So while reading template argument deduction page i found this :
If a non-type template parameter is used in the parameter list, and
the corresponding template argument is deduced, the type of the
deduced template argument ( as specified in its enclosing template
parameter list, meaning references are preserved) must match the type
of the non-type template parameter exactly, except that cv-qualifiers
are dropped, and except where the template argument is deduced from an
array bound—in that case any integral type is allowed, even bool
though it would always become true:
As always, of course, you must read few more times than once to understand what it means :)
So an interesting result comes out.
Already our desired specialization is not selected but if the compiler had been forced to select, it would be an error.
template<typename T1, int SIZE>
void foo(std::vector<std::array<T1, SIZE>> bar) {
std::cout << "SPECIFIC (array)" << std::endl;
}
int main() {
std::vector<std::array<int, 3>> b(2, std::array<int, 3> {4, 5, 6});
foo(b); // P = std::vector<std::array<int,(int)SIZE>
// A = std::vector<std::array<int,(unsigned_long)SIZE>>
// error: deduced non-type template argument does not have the same
// type as its corresponding template argument */
}
run code
Another interesting thing is :
If the non-type template argument had not been deduced, there would be no restriction which forces argument and template types to be same.
#include <vector>
#include <array>
#include <iostream>
template<typename T1, int SIZE>
void foo(std::vector<std::array<T1, SIZE>> bar) {
std::cout << "SPECIFIC (array)" << std::endl;
}
int main() {
std::vector<std::array<int, 3>> b(2, std::array<int, 3> {4, 5, 6});
foo<int,3>(b);
}
run code
I think this is simply due to one line from [temp.deduct.call]/4
In general, the deduction process attempts to find template argument values that will make the deduced A identical to A
To clarify, A means the parameter, from [temp.deduct.call]/1
...template argument deduction with the type of the corresponding argument of the call (call it A)...
As has already been pointed out, changing template<typename T1, int SIZE> to template<typename T1, size_t SIZE> fixes the issue you are seeing. As stated in [temp.deduct.call]/4, the compiler is looking to deduce an A that is identical to A. Since an std::array has template arguments <class T, size_t N> (from [array.syn]), it's second parameter is in fact size_t, not int.
Therefore, for the template deduction, your generic function of template<typename T1> is able to match exactly the type of A, where-as your specialized template<typename T1, int SIZE> is not an exact match. I believe MSVC is incorrect in its deduction.
I have written this code in which if I uncomment the 2nd last line I get error - "template argument deduction/substitution failed: ". Is it because of some limit to generic functions in C++? Also my program doesn't print floating answer for the array b. Is there anything I can do for that? (sorry for asking 2 questions in single post.)
P.S: I have just started learning C++.
#include <iostream>
using namespace std;
template <class T>
T sumArray( T arr[], int size, T s =0)
{
int i;
for(i=0;i<size;i++)
{ s += arr[i];
}
return s;
}
int main()
{
int a[] = {1,2,3};
double b[] = {1.0,2.0,3.0};
cout << sumArray(a,3) << endl;
cout << sumArray(b,3) << endl;
cout << sumArray(a,3,10) << endl;
//cout << sumArray(b,3,40) << endl; //uncommenting this line gives error
return 0;
}
EDIT 1: After changing 40 to 40.0, the code works. Here is the output I get:
6
6
16
46
I still don't get the floating answer in 2nd case. Any suggestion ?
The reason is that compiler can not deduce the type for T.
How it should understand what T is for your last example? The type of the first argument (b) is double[], while it is T[] in the function definition. Therefore it looks like that T should be double. However, the type of the third argument (40) is int, so it looks like T should be int. Hence the error.
Changing 40 to 40.0 makes it work. Another approach is to use two different types in template declaration:
#include <iostream>
using namespace std;
template <class T, class S = T>
T sumArray( T arr[], int size, S s =0)
{
int i;
T res = s;
for(i=0;i<size;i++)
{ res += arr[i];
}
return res;
}
int main()
{
int a[] = {1,2,3};
double b[] = {1.0,2.0,3.1};
cout << sumArray(a,3) << endl;
cout << sumArray(b,3) << endl;
cout << sumArray(a,3,10) << endl;
cout << sumArray(b,3,40) << endl; //uncommenting this line gives error
return 0;
}
Note that I had to cast s to T explicitly, otherwise the last example will lose fractional part.
However, this solution will still not work for sumArray(a,3,10.1) because it will cast 10.1 to int, so if this is also a possible use case, a more accurate treatment is required. A fully working example using c++11 features might be like
template <class T, class S = T>
auto sumArray(T arr[], int size, S s=0) -> decltype(s+arr[0])
{
int i;
decltype(s+arr[0]) res = s;
...
Another possible improvement for this template function is auto-deduction of array size, see TartanLlama's answer.
sumArray(b,3,40)
The type of 40 is int, but the type of b is double[3]. When you pass these in as arguments, the compiler gets conflicting types for T.
A simple way to fix this is to just pass in a double:
sumArray(b,3,40.0)
However, you would probably be better off allowing conversions at the call site by adding another template parameter. You can also add one to deduce the size of the array for you so that you don't need to pass it explicitly:
template <class T, class U=T, std::size_t size>
U sumArray(T (&arr) [size], U s = 0)
The U parameter is defaulted to T to support the default value for s. Note that to deduce the size of the array, we need to pass a reference to it rather than passing by value, which would result in it decaying to a pointer.
Calling now looks like this:
sumArray(b,40)
Live Demo
In
template <class T>
T sumArray( T arr[], int size, T s =0)
^ ^
Both (deducible) T should match.
In sumArray(b, 3, 40), it is double for the first one, and int for the second one.
There is several possibilities to fix problem
at the call site, call sumArray(b, 3, 40.0) or sumArray<double>(b, 3, 40);
Use extra parameter:
template <typename T, typename S>
auto sumArray(T arr[], int size, S s = 0)
Return type may be T, S, or decltype(arr[0] + s) depending of your needs.
make a parameter non deducible:
template <typename T> struct identity { using type = T;};
// or template<typename T> using identity = std::enable_if<true, T>;
template <typename T>
T sumArray(T arr[], int size, typename identity<T>::type s = 0)
Should be
sumArray(b,3,40.0)
so, T will be deduced to double. In your code it's int.
Another option when deduction fails is to explicitly tell the compiler what you mean:
cout << sumArray<double>(b,3,40) << endl;
The compiler does not know whether T should be int or double.
You might want to do the following, in order to preserve the highest precision of the types passed:
template <class T, class S>
std::common_type_t <T, S> sumArray (T arr [], std::size_t size, S s = 0)
{
std::common_type_t <T, S> sum = s;
for (std::size_t i = 0; i != size; ++i)
{
sum += arr[i];
}
return sum;
}
The function you are writing, however, already exists. It's std::accumulate:
std::cout << std::accumulate (std::begin (b), std::end (b), 0.0) << std::endl;
Templates only accept one type of data, for example if you send an array of double, then the runtime will deduce :
Template = double[]
so every time he will see it he will expect an array of doubles.
sumArray(b,3,40) passes "b" (which is an array of doubles) but then you pass "40" which the runtime cannot implicitly convert to double.
So the code
sumArray(b,3,40.0)
will work
Just wondering - i've written following function
template <class T, size_t N>
T* asFlatArray (T arr[][N])
{
// some code
}
and call it like
asFlatArray(myArray); // where myArray is int myArray[some_size][sime_size];
compilation runs without errors. But if i change 'arr' to reference like
template <class T, size_t N>
T* asFlatArray (T (&arr)[][N])
{
// some code
}
i'll have errors:
parameter ‘arr’ includes reference to array of unknown bound ‘T [][N]’
I know how to fix it
template <class T, size_t Rows, size_t Columns>
T* asFlatArray (T (&arr)[Rows][Columns])
but i don't understand why is it's happening?
The fundamental problem is that the first function template is equivalent to
template <class T, size_t N>
T* asFlatArray (T (*arr)[N]) {}
so there is no dimension to calculate. It will match a pointer to an array, or anything that can decay to pointer to array. Thus is can also match 2D array.
As for the second example, a reference or pointer to type T[][N] cannot be a function parameter, which leads to a compiler error. Of course, you can add an extra template parameter for the missing dimension:
template <class T, size_t N, size_t M>
T* asFlatArray(T (&arr)[N][M]) {}
C++11 and C++14 standards in part [dcl.fct]/8 (8.3.5) explicitly state:
If the type of a parameter includes a type of the form “pointer to array of unknown bound of T” or “reference to array of unknown bound of T,” the program is ill-formed.
There is actually a core language active issue proposing to remove that from the standard, so perhaps we won't see it in C++17.
BTW, if the array size is really unknown, clang (but not gcc) compiles the code:
#include <iostream>
class A {
public:
static int arr[][3];
};
// this compiles on clang but not gcc
void func(int (&arr_ref)[][3]) {
std::cout << arr_ref[1][2] << std::endl;
}
int main() {
int (&arr_ref)[][3] = A::arr;
std::cout << A::arr[1][2] << std::endl;
arr_ref[1][2] = 100;
std::cout << A::arr[1][2] << std::endl;
arr_ref[1][2] = 200;
func(arr_ref);
}
int A::arr[][3] = {{1, 2, 3}, {4, 5, 6}};
Demo