Smaller c++ questions

Pages: 12
I am going to be reviewing my C++ from scratch and will have smaller and sillier questions. Instead of making many posts, I will just put them in here as I review things. First one is...

QUESTION 1:
Both string literals from Test1 and Test2, get passed as char arrays...const char[5]"Jack". In Test1 the char array is sent to a string class and the overloaded == operator is used and the comparison works fine.
1
2
3
4
5
6
7
8
	//Test 1
	string name1("Jack"), name2("John");
	DisplayComparison(name1, name2);

	//Test 2
	DisplayComparison("Jack", "John");
	//prints "Jack" if Test 1 is commented out
	//Prints "John" if Test 1 is NOT commented out 

[/code]

The problem is in Test 2. If I COMMENT OUT Test1 then Test 2 gets evaluated as "Jack" as being greater than "John", which you don't want. But if I UNCOMMENT Test1 then "John" gets evaluated as greater than "Jack", which is what you want.

So comparisons work fine with strict char and string variables, but there is an issues with char arrays, probably because they are deprecated to pointers and the pointer addresses are then compared and not the ASCII values, is that right????

So if I did not want to set Test 2 as strings, I could then compare each char value in the char arrays with one another as a possible solution, but is there a better, built-in or more cunning way to do it?
arrayJack[0] > arrayJohn[0]; //(J > J) FALSE
arrayJack[1] > arrayJohn[1]; //(a > o) FALSE

Is there a way to force the comparison of the whole array values with one another? How can I make the program just work correctly, when I use "DisplayComparison("Jack", "John");" ?


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
#include <iostream>

using namespace std;

template <typename Type>
const Type& GetMax(const Type& val1, const Type& val2)
{
	if (val1 > val2)
		return val1;
	else
		return val2;
}

template<typename Type>
void DisplayComparison(const Type& val1, const Type& val2)
{
	cout << "Max of (" << val1 << ", " << val2 << ") = " << GetMax(val1, val2) << endl;

}

int main()
{
	//Test 1
	string name1("Jack"), name2("John");
	DisplayComparison(name1, name2);

	//Test 2
	DisplayComparison("Jack", "John");
	//prints "Jack" if Test 1 is commented out
	//Prints "John" if Test 1 is NOT commented out

	//Test3
	DisplayComparison<string> ("Jack", "John");


	//Test 4 (ASCII  H = 72,  J = 74)
	DisplayComparison("H", "J");	//prints 'H'???
	DisplayComparison("J", "H");	//prints 'H'???
	
	char char1 = 'H';	//ASCII  72
	char char2 = 'J';	//ASCII  74
	DisplayComparison(char1, char2);	//prints 'J'
	DisplayComparison(char2, char1);	//prints 'J'



	cout << "*****************************\n\n";

	if (char1 > char2)
		cout << "Char1 is greater\n";
	else
		cout << "Char1 is less\n";
	cout << endl;


	//	DisplayComparison(10, 20);
	//	DisplayComparison(3.14, 3.1415);
	return 0;
}

they are deprecated to pointers and the pointer addresses are then compared and not the ASCII values, is that right


Yes. To compare arrays of null-terminated char, use strcmp().

You could use "Jack"s - which forces this to be of type std::string.

You could always have a template specialisation for type const char*.

Thanks, they all work great.

1
2
3
4
        DisplayComparison("Jack"s, "John"s);
	cout << strcmp("Jack", "John") << endl;		//-1
	cout << strcmp("John", "Jack") << endl;		//1
	cout << strcmp("John", "John") << endl;		//0 


Templatized and specialized DisplayComparison method 1:
1
2
3
4
5
6
template<typename Type> void DisplayComparison(const char* val1, const char* val2)
{
	string value1(val1);
	string value2(val2);
	cout << "Max of (" << val1 << ", " << val2 << ") = " << GetMax(value1, value2) << endl;
}


Templatized and specialized DisplayComparison method 2:
1
2
3
4
5
6
7
8
9
10
template<typename Type> void DisplayComparison(const char* val1, const char* val2)
{
	short int tempInt= strcmp(val1, val2);
	if (tempInt < 0)
		cout << "Max of (" << val1 << ", " << val2 << ") = " << val2 << endl;
	else if (tempInt > 0)
		cout << "Max of (" << val1 << ", " << val2 << ") = " << val1 << endl;
	else
		cout << "Max of (" << val1 << ", " << val2 << ") = " << "NONE, they are equal!" << endl;
}



I wonder why this line would not work though, I thought the template type would be deduced?
template<> void DisplayComparison<const char*>(const char* val1, const char* val2)


Last edited on
> Templatized and specialized

Templatized and overloaded (not specialised)

For function templates, overloading is the right approach.
See: "Why Not Specialize Function Templates?" http://www.gotw.ca/publications/mill17.htm


> wonder why this line would not work though, I thought the template type would be deduced?

template<> void DisplayComparison<const char*>(const char* val1, const char* val2)
is not a valid specialisation of (does not match) the primary template
template<typename Type>
void DisplayComparison(const Type& val1, const Type& val2)


This (const char* passed by reference to const) would be a specialisation:
template<> void DisplayComparison<const char*>(const char* const& val1, const char* const& val2)
My jaw dropped.......into a blender. A simple question leads to another rabbit hole, but at this point I shouldn't be surprised. My book showed one example of a template specialization for a class without any parameters, and there I was thinking this was easy and that I knew what I was doing.

1) So, before this post I thought you can specialize with any one of these (assumed without trying):

1
2
3
4
5
6
template<> void DisplayComparison<const char*>(int& a, int& b){}

template<> void DisplayComparison<const char*>(int a, int b){}

template<> void DisplayComparison<const char*>(double a, double b){}


...but no, no you can't! They work more specifically than I assumed.


template<> void DisplayComparison<const char*>(const char* const& val1, const char*...
......................................................^^^^^^
You can change the const char* there to any type you want, but you can't change the type for the parameters inside (). They must be the same type as the base template. And you probably need the same number of parameters too (have not played with changing them yet to find out). Otherwise, you really don't need a specialization...just use a separate function on its own!

1
2
3
4
template<typename Type>
void DisplayComparison(const Type& val1, const Type& val2)

template<> void DisplayComparison<const char*>(const char* const& val1, const char* const& val2)


So, since there are 2 parameters with a const & reference, then the specialization must have two parameters with a const & reference.

Did I get this right????????????????????

2) Thanks for the link, so precious!
1
2
3
4
5
6
7
8
9
10
11
12
13
template<class T> class X { /*...*/ };      // (a)
template<class T> void f( T );              // (b)
template<class T> void f( int, T, double ); // (c)
template<class T> void f( T* );             // (d)
template<> void f<int>( int );              // (e)
void f( double );                           // (f)


f( b );        // calls (b) with T = bool 
f( i, 42, d ); // calls (c) with T = int 
f( &i );       // calls (d) with T = int 
f( i );        // calls (e) 
f( d );        // calls (f) 


I tried covering his examples to see if I can guess the function called. I was able to guess all of them right, except for one.

f( &i ); // calls (d) with T = int

I was upset at this, because it is a reference to an int and I was sure it would call (e)... f( i );
But no, it calls the template function for a pointer because it provides an address to the pointer. Very sneaky and you have to be extremely careful. Hard for me to catch as a newb.

 
template<class T> void f( T* );             // (d) 


Last edited on
> So, since there are 2 parameters with a const & reference,
> then the specialization must have two parameters with a const & reference.

> Did I get this right????????????????????

Yes. For template argument deduction to work, the two arguments must be of the same type T


> .just use a separate function on its own!

Yes, yes, yes! Overloading is far more intuitive to most programmers.

In general avoid specialising function templates. For example, this may be surprising to many:

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
#include <iostream>

template < typename T > void foo( const T&, const T& ) { std::cout << "base template\n" ; }

template <> void foo( const char* const&, const char* const& ) { std::cout << "specialisation for T == const char*\n" ; }

int main()
{
    foo( 22, 33 ) ; // base template T == int

    foo( "abcd", "efgh" ) ; // base template T == const char[5]

    // foo( "abcd", "efghijkl" ) ; // *** error: no matching function; 
                                   //            conflicting types foo( const char[5], const char[9] ) 

    char cstr1[] = "abcd", cstr2[] = "efgh" ;
    foo( cstr1, cstr2 ) ; // base template T == const char[5];

    char* p1 = cstr1, *p2 = cstr2 ;
    foo( p1, p2 ) ; // base template T == char*

    const char* pc1 = p1, *pc2 = p2 ;
    foo( pc1, pc2 ) ;

    // foo( pc1, p2 ) ; // *** error: no matching function: 
                        //     conflicting types foo( const char*, char* )

    foo<const char*>(p1,p2) ; // specialisation for T == const char*
    foo<const char*>(pc1,p2) ; // specialisation for T == const char*
    foo<const char*>(pc1,"xyz") ; // specialisation for T == const char*
}

http://coliru.stacked-crooked.com/a/b1fd1bb81ba80e9e
1) This one was tricky...
 
foo( "abcd", "efgh" ) ; // base template T == const char[5] 

Because the string literal is a const char array, and I thought it did not get copied over but gets deprecated to a const char* so that it would satisfy the specialization. But no, probably because it was not explicitly sent as a pointer* from main()...that is why base template gets called?

2) If you bypass the compiler decision making and call the specialization manually, then the parameters are not so picky.

1
2
3
4
 
foo<const char*>(p1,p2) ; // specialisation for T == const char*
foo<const char*>(pc1,p2) ; // specialisation for T == const char*
foo<const char*>(pc1,"xyz") ; // specialisation for T == const char* 



3) It is perfectly legit to mix and match like this, the int 2nd parameter with the typename?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//template<typename T1, typename T2>
//void Func1(const T1& t1, const T2& t2)
template<typename T1>
void Func1(const T1& t1, int t2)
{
	cout << "T1(double) = " << t1 << " and int = " << t2 << endl;
}

int main()
{
	
	Func1<> (2.2, 10);


	return 0;
}

4) Just curious, how long into your c++ career did you stumble onto this and what did you think when you first made the discovery and was forced to investigate this deeper?

Last edited on
> But no, probably because it was not explicitly sent as a pointer* from main()...
> that is why base template gets called?

For overload resolution, only the base function templates (and non template functions if any) are looked at. Only after the overload is resolved, and the resolution selects a base function template, does the compiler attempt to see if there is a specialisation that could be used.

Here, the selected base template accepts arrays (arrays can't be copied, but they can be passed by reference) and the specialisation for pointers does not apply, there is no array to pointer decay.


> Just curious, how long into your c++ career did you stumble onto this
> and what did you think when you first made the discovery and was forced to investigate this deeper?

Fortunately, I came across this quite late. It was surprising, but not shocking: after some thought, it seemed reasonable that though a user of the code can add any number of specialisations of their own, those specialisations would not derail the overload resolution that was intended by the author of the original code.
QUESTION 2: (in my review smaller question set)
I can use the string "str1.find("day", 0);" find method just fine, but can one use the algorithm "find" with strings? Does the val not accept strings or is there a work around for it? I know one way would be by searching & matching ASCII values for 'd', 'a', & 'y' in nested if statements, but is there a built-in and better way? Or do we just use the str1.find() method instead?


https://cplusplus.com/reference/algorithm/find/
"val
Value to search for in the range.
T shall be a type supporting comparisons with the elements pointed by InputIterator using operator== (with the elements as left-hand side operands, and val as right-hand side)."

1
2
3
4
5
6
7
8
9
10
	//DOES NOT WORK!!!
	//string::iterator findChar;
	string::const_iterator findChar;
	findChar = find(str1.begin(), str1.end(), "day");

	if (findChar != str1.cend())
	{ 
		cout << "day found at pos = " << distance(str1.cbegin(), findChar) << endl;
	}
	cout << "NOT FOUND!\n\n";





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
#include <iostream>
#include <string>
#include <vector>
using namespace std;

int main()
{
	string str1("Hello_World on day 1! What a time to be alive today!");

	//THIS WORKS!!! Find first "day"
	size_t charPos = str1.find("day", 0);
	if (charPos != string::npos)
	{
		cout << "day found at pos = " << charPos << endl << endl;
	}
	else
		cout << "NOT FOUND!\n\n";
	

	//THIS WORKS!!! Find all "day" matches
	charPos = str1.find("day", 0);
	while (charPos != string::npos)
	{
		cout << "day found at pos = " << charPos << endl;
		charPos = str1.find("day", charPos + 1);
	}
	cout << endl << endl;


	//DOES NOT WORK!!!
	//string::iterator findChar;
	string::const_iterator findChar;
	findChar = find(str1.begin(), str1.end(), "day");

	if (findChar != str1.cend())
	{ 
		cout << "day found at pos = " << distance(str1.cbegin(), findChar) << endl;
	}
	cout << "NOT FOUND!\n\n";
	
	
	//THIS WORKS!!!
	vector<int> myInt{ 10,20,30,40,50 };
	vector<int>::const_iterator iter30 = find(myInt.cbegin(), myInt.cend(), 30);

	if (iter30 != myInt.cend())
		cout << "30 found at pos = " << distance(myInt.cbegin(), iter30) << endl << endl;
	else
		cout << "30 NOT FOUND!!!\n\n";



	return 0;
}
You can use std::find() with std::string. However you need to be careful re the use. std::find() treats a std::string as a container of char. .begin() is the start iterator and .end() is the one-past-end iterator. As the iterators point to one element - which in this case is of type char - std::find() will only work when trying to find a single char in a string - not when trying to find a string within a string. For that you need .find().
Thanks, thought that might be the answer unless there was some hidden trick. I guess they did not overload the std::find() for a string val, because they knew they had .find() already and that it was reliable.
> they did not overload the std::find() for a string val, because they knew they had .find() already

There are two families of algorithms:
std::find to find a single element and
std::search to locate a sub-sequence of elements.
https://en.cppreference.com/w/cpp/algorithm/search
Using std::search or std::ranges::search isn't quite as easy as using .find() for strings. For std::search you need to pass start()/end() iterators to the sequence to search for. With std::ranges you can just pass a container that holds what to find - providing begin() and end() can be obtained. Also note the different return type. Consider:

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
#include <string_view>
#include <algorithm>
#include <iostream>

using namespace std::string_view_literals;

int main() {
	constexpr std::string_view tofind { "day" };
	constexpr std::string_view str1 { "Hello_World on day 1! What a time to be alive today!" };

	const auto findChar { std::search(str1.cbegin(), str1.cend(), tofind.cbegin(), tofind.cend()) };

	const auto rangeChar { std::ranges::search(str1, "day"sv) };

	if (findChar != str1.cend())
		std::cout << "day found at pos = " << std::distance(str1.cbegin(), findChar) << '\n';
	else
		std::cout << "NOT FOUND!\n\n";

	if (!rangeChar.empty())
		std::cout << "day found at pos = " << std::distance(str1.cbegin(), rangeChar.begin()) << '\n';
	else
		std::cout << "NOT FOUND!\n\n";

}



day found at pos = 15
day found at pos = 15

Last edited on
Thanks, good to know about std::search & std::ranges::search, and I am sure there are tons more in the STL that I am not aware of yet.

If I did the behind the scenes programming for find(), I would have just overloaded it for chars & strings to make it contiguous. They both have 3x overloads each, so 6x total needed, but I am sure they had a good reason to separate them.

I always wished that I had a programming mentor around me for years & I would have looked at C++ much sooner. Just a few months ago I opened up this book and read the first few pages, then I skimmed the programs in the rest of the book and thought...man I will never be able to understand this. Now, it is amazing that I can read the code in these books and I look forward to eventually looking at larger public code. Same with the SFML, as 3 months ago I did not know what it even was. Now, I am kicking myself for not having done this sooner, as I would have been so much further ahead.

QUESTION 2:
I can appreciate the .end()/.cend() as it makes things convenient to go 1 past the end, but maybe it is still too early for me to appreciate the string copy below that goes +1 beyond for the copy. Were you too bothered by this at first, because not only are you tracking zero based elements/subscript, but now you have to account for extra +1...probably because of .end() consistency? I am sure there is a reason for this too, but I would have just kept the .end() the way it is & made this copy constructor not need to go +1 beyond.

//[0] [1] [2] [3] [4] [5]
//.begin() .begin() +1 .begin()+2 .begin()+3 .begin()+4 .begin()+5


1
2
3
4
5
6
7
	vector<int> myVec{ 10,20,30,40,50,60 };
	vector<int>::const_iterator iter = myVec.cbegin() + 5;
	cout << *iter << endl << endl;

	std::vector<int> partialCopy(myVec.cbegin(), myVec.cbegin() + 5); //copy 1st 5
	for (auto i : partialCopy)
		cout << i << endl;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <vector>
#include <iostream>
#include <algorithm>

int main() {
	const std::vector myVec { 10, 20, 30, 40, 50, 60 };
	const auto iter { myVec.cbegin() + 5 };

	std::cout << *iter << "\n\n";

	std::vector<int> partialCopy { myVec.cbegin(), iter }; //copy 1st 5

	for (auto i : partialCopy)
		std::cout << i << '\n';
}


L7 refers to the 60 element (iterator referring to 5th element)

L11 refers to elements starting at begin() up to but not including the iterator iter. When an end iterator is used, this points one-past the end of that required. So a loop terminates when an iterator == end_iterator. This is true of any container for which iterators are available/supported - including std::string.


60

10
20
30
40
50


When using std::string, forget about c-styled null-terminated strings. std::string can contain null chars within the stored string. Yes, there is a null char at the end so that .c_str() correctly returns a null-terminated const char* type - but for c++ forget about this!

1
2
3
4
5
6
7
8
9
10
11
#include <vector>
#include <iostream>
#include <algorithm>
#include <string>

int main() {
	const std::string foobar { "foobar" };

	for (auto itr { foobar.cbegin() }; itr != foobar.cend(); ++itr)
		std::cout << static_cast<unsigned int>(*itr) << "  ";
}



102  111  111  98  97  114


With std::string (and NOT other std containers), you can dereference .end() iterator for non-empty to get 0 - but only because it is known that std::string stores a null at the end of the stored data for c-string compatibility (which is really implementation). You shouldn't need to (don't!) de-reference .end() for a non-empty string as it's null! - and don't de-reference a .end() iterator for other containers.
Last edited on
Sorry, I was not being clear. I took the long way with the last code I posted just for practice purpose & avoided auto and reusing "iter" just for the sake of practice...except for the for loop as I have done enough of those.

I know that .begin/cbegin points to the first element and that an .insert(1) at it will place an item to the left of "10" ...so that 1, 10, 20, 30...

And that .end()/.cend() points to one past the last element. Good reminder about being able to reference .end() for a null terminated string as I wouldn't have dared, but yes I see now that you could.

What I am really asking is WHY did they decide to treat "myVec.cbegin() + 5);" as if it were a .cend()?????? I understand that you specify beginning of string to end of string in the arguments, but IT IS NOT A .cend()....so why did they choose this way that can throw one off and one can make a mistake. More room for possible mistakes. There must be a reason??????

 
std::vector<int> partialCopy(myVec.cbegin(), myVec.cbegin() + 5);



You don't do that for arrays & you don't do that for the snippet below "myVec.cbegin() + 5;"....refers to element 60....the way it should be. It does not mean refer to one element less...the 50. Then all of a sudden "myVec.cbegin() + 5;" above is treated like a .cend().

 
vector<int>::const_iterator iter = myVec.cbegin() + 5;



If I did not see any documentation or examples, I would have expected the below to take the first 5 elements, and I would have been happy. Can you convince me otherwise, that this is a good thing they did??? WHY did they do it this way???

 
std::vector<int> partialCopy(myVec.cbegin(), myVec.cbegin() + 4);



Good review of containers George, thanks.

I think it was a trap for them. Since they used the .end()'s the way they did, they are forced into this and there is no way out...and therefore breaking continuity.

.cend();//returns an ADDRESS to 1 past end
.cbegin/;//returns an ADDRESS to beginning.

 
std::vector<int> partialCopy(myVec.cbegin(), myVec.cbegin() + 5);


The 2nd parameter "myVec.cbegin() + 5" knows nothing that it is a .cbegin() once it gets to the other side of the method and cannot distinguish itself from a "myVec.cend()". Because after all both on the method side are just address locations and the overloaded copy method knows nothing about whether a begin or end was sent in. I was thinking that it might "somehow" identify itself at the other end, but after some more thought I see now that it does not....maybe even cannot...since the return is already used for an address and not as an identifier? I think that might be the answer then?



If I did not see any documentation or examples, I would have expected the below to take the first 5 elements, and I would have been happy. Can you convince me otherwise, that this is a good thing they did??? WHY did they do it this way???

The rationale for using one-past-the-end iterators is the same as the rationale for using zero-based indices (offsets) in arrays and < in loops. Dijkstra explained it:
https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html
Not only would it be inconsistent to do this any other way, any alternative would have inherent disadvantages.
Last edited on
Good read, thanks.

I have grown to like and appreciate zero based arrays, especially when it comes to pointers and addressing. So much easier to have a pointer p* to the [0] element, and then (P* + 1) to get element [1], and I can appreciate access from that perspective.

But here it is the same, yet a little different. "myVec.cbegin() + 5" has two meaning depending on where your sending it, the partial copy or referring to the element.

So lets ask another way. How often do you find bugs/errors from others, or have caught yourself getting the wrong range for containers? Eh, why not same question for the zero-based arrays while we are on this relevant topic.

The focal point is not on this smaller sample, but on programs with thousands and millions of lines. Office party with some drinking and then having to go back to crunch or a massive headache one day and things can get a little blurry when your staring at code for hours.
Pages: 12