What is this exercise asking for?

PPP2 Chapter 19 Exercise 2. Specs:
2. Write a template function that takes a vector<T> vt and a vector<U> vu as
arguments and returns the sum of all vt[i]*vu[i] s.


What is it asking for with the U? Is it just some arbitrary type for vector, like T, and he just named it U because U comes after T in the alphabet?
Last edited on
Is it just some arbitrary type for vector, like T, and he just named it U because U comes after T in the alphabet?
Yes. A template function that takes two vectors of two independent types.
So it could be elements of a vector<int> multiplied by elements of a vector<double>, or something like that? But then what if one of the types is a string? Can you multiply strings?
So it could be elements of a vector<int> multiplied by elements of a vector<double>, or something like that? But then what if one of the types is a string? Can you multiply strings?


Code which attempts to do that (multiply an int by a string, e.g.) should fail to compile.
Templates generate code for you. If there's no way to generate code that makes sense, then compilation fails.

Write the code as-if it would only ever be passed types that make sense.
Of course, it could be possible to overload operator*(double, const std::string &), in which case the function would compile.
Can't you just throw an exception if someone tries to call that?
Can't you just throw an exception if someone tries to call that?

It can be done, but doing so would usually be a bad decision: why wait until run-time to catch an error that could be caught at compile-time? (It would also require extra effort.)

All templates are meta-programs - instructions for the compiler, explaining how to write code for you. And when you use a template, the compiler tries to write the code you asked for. If the resulting code is ill-formed, so is your program.
Last edited on
Good point. Okay, how about this?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// Osman Zakir
// 10 / 22 / 2017
// Bjarne Stroustrup: Programming: Principles and Practice Using C++ 2nd Edition
// Chapter 19 Exercise 2
// Exercise Specifications:
/**
 * Write a template function that takes a vector<T> vt and a vector<U> vu as
 * arguments and returns the sum of all vt[i]*vu[i]s.
 */

#include "../../cust_std_lib_facilities.h"
#include <stdexcept>
#include <iostream>
#include <vector>

template<typename T, typename U>
std::vector<T> calc_product(const std::vector<T> &vt, const std::vector<U> &vu);
void fill_v_int(std::vector<int> &v);
void fill_v_double(std::vector<double> &v);

int main()
{
	try
	{	
		std::vector<int> vint;
		std::vector<double> vdouble;
		std::cout << "Please enter 5 integers:\n";
		fill_v_int(vint);
		std::cout << "Now enter 5 real numbers (i.e. \"real numbers\" and not just integers:\n";
		fill_v_double(vdouble);
		std::vector<double> prod_v = calc_product(vdouble, vint);
		std::cout << "The product of the real numbers multiplied by integers is:\n";
		for (std::size_t i = 0; i < prod_v.size(); ++i)
		{
			std::cout << prod_v[i] << '\n';
		}
		keep_window_open();
	}
	catch (const std::exception &e)
	{
		std::cerr << "Exception: " << e.what() << '\n';
		keep_window_open();
		return 1;
	}
}

template<typename T, typename U>
std::vector<T> calc_product(const std::vector<T> &vt, const std::vector<U> &vu)
{
	std::vector<T> prod;
	for (std::size_t i = 0; i < vt.size(); ++i)
	{
		prod.push_back(vt[i] * vu[i]);
	}
	return prod;
}

void fill_v_int(std::vector<int> &v)
{
	int num = 0;
	for (int i = 0; i < 5; ++i)
	{
		std::cin >> num;
		std::cin.ignore(32767, '\n');
		v.push_back(num);
	}
	std::cin.ignore(32767, '\n');
	std::cin.clear();
}

void fill_v_double(std::vector<double> &v)
{
	double num = 0;
	for (int i = 0; i < 5; ++i)
	{
		std::cin >> num;
		std::cin.ignore(32767, '\n');
		v.push_back(num);
	}
	std::cin.ignore(32767, '\n');
	std::cin.clear();
}


I tried allowing for an arbitrary number of elements in the vectors by using while loops that would end when anything other than a number was entered, but that made the second loop end prematurely and the products came out to be 0s.
Question: What if T * U is of type V? How would you declare prod?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * Write a template function that takes a vector<T> vt and a vector<U> vu as
 * arguments and returns the sum of all vt[i]*vu[i]s.
 */

template< typename T, typename U >
auto inner_product_v1( const std::vector<T>& vt, const std::vector<U>& vu )
{
    const decltype( vt.front()*vu.front() + vt.front()*vu.front() ) zero {} ;
    return std::inner_product( std::begin(vt), std::begin(vt) + std::min( vt.size(), vu.size() ),
                               std::begin(vu),  zero ) ;
}

template< typename T, typename U >
auto inner_product_v2( const std::vector<T>& vt, const std::vector<U>& vu )
{
    decltype( vt.front()*vu.front() + vt.front()*vu.front() ) result {} ;
    for( std::size_t i = 0 ; i < std::min( vt.size(), vu.size() ) ; ++i ) result += vt[i]*vu[i] ;
    return result ;
}
What header file is that function in?

And I need you to explain to me what's going on there. What does the function inner_product() actually do, and how?
> What header file is that function in?

#include <numeric>


> What does the function inner_product() actually do

See: http://en.cppreference.com/w/cpp/algorithm/inner_product


> and how?

The cppreference page has 'Possible implementation'
It's asking for a pointer to the first and last element, right? So how does the way you called std::min() give a pointer to the last element? I think I understand everything else going on there.
> It's asking for a pointer to the first and last element, right?

Iterators to the first and last elements of the first range and an iterator to the beginning of the second range.


> how does the way you called std::min() give a pointer to the last element?

The iterator for a vector is a random access iterator.
std::begin(vt) + std::min( vt.size(), vu.size() ) yields an iterator to the element
at std::min( vt.size(), vu.size() positions away from the beginning of the sequence. Note that the problem statement does not specify that there is an invariant that the two vectors must be of the same size.
DragonOsman wrote:
What is it asking for with the U? Is it just some arbitrary type for vector, like T, and he just named it U because U comes after T in the alphabet?


The use of letters like T or U is just a convention and very common. However they can be any alphabetical identifier. Probably the type of thing where this is sensible to use is in Policy Design:

https://en.wikipedia.org/wiki/Policy-based_design

See how they had:
template <typename OutputPolicy, typename LanguagePolicy>

They could have had:

template <typename A, typename B>

But that would have been much less readable throughout the code.

If you are not doing Policy Design, then stick to using any Capital letter of the alphabet to represent a possible type.

The thing about templates is that they are a whole different kettle of krawldads. Scott Meyers gives them their own paradigm. The 4 paradigms he lists are: C; OOP C++ ; Template C++ ; and the STL. I reckon Template Meta Programming (TMP) could also deserve it's own paradigm, it is arguably quite different to everything else. These days, TMP seems pervasive throughout the STL.



@JLBorges,
Is there any particular significance to using
const decltype( vt.front() * vu.front() + vt.front() * vu.front() ) zero{};
rather than just
const decltype( vt.front() * vu.front() ) zero{};
?

Even if it's possible in principle, I'm struggling to come up with a sensible example for where somebody might define the addition operator to return a result type distinct from the common type of its two operands.
It would be good for defining a good type for the resulting variable to be, right?

And that's what decltype does, then? Declaring a type?
> define the addition operator to return a result type distinct from the common type of its two operands.

The simplest example of a binary + operator where the result type distinct from the common type of its two operands: char + char == int

Binary + operator where there is no common type between the types of its two operands:
pointer + int, iterator + int, std::string + char

Binary - operator between two operands of the same type yielding another type:
pointer - pointer, iterator - iterator

Though in this particular exercise, there is also a binary * operator involved, which strongly suggests some kind of numeric types; this makes it quite unlikely that a*b and a*b + a*b would yield results of different types.
Last edited on
Topic archived. No new replies allowed.