Questions about Templates

Started by
30 comments, last by Kylotan 6 years, 7 months ago

When you say something is "not compiling", it's normally polite to give the error message. :) That usually tells you everything you need to know...

 

Advertisement
14 minutes ago, Kylotan said:

When you say something is "not compiling", it's normally polite to give the error message. :) That usually tells you everything you need to know...

 

Good point, I didn't yet sorted out the problem of my VC being in Italian, but I'll paste the error link :P

I'm getting 2 Compiler Error C2109 on line 52 and 53 where I try to  use Val[ID] inside my "if" block

Quote

subscript requires array or pointer type

And 2 Compiler Error C2679 on line 58 and 59 where I try to use Val inside my "else" block

Quote

binary 'operator' : no operator found which takes a right-hand operand of type 'type' (or there is no acceptable conversion)

But from the screenshot is clearly visible that Val is a vector<int>& therefore I don't get it.. well, since it doesn't run it is failing at compile time or before, so I assume this is because at that time is not known what Val will be, and is only know later on. So how do I get it to "assume" that I will provide a valid variable with those operators (operator[], operator<<, operator=) at run-time?! x_x

Ok, trough commenting and uncommenting I think I've realized what is going on here:


template <typename T, typename V>
void set(MyContainer<T>& e, V newVal, int ID = 0)
{
	T& Val = e.get();
	if (is_Container<T>::value)
	{
		cout << "is a vector" << endl;
		//cout << Val[ID] << endl;
		//Val[ID] = newVal;
	}
	else
	{
		cout << "is not a container" << endl;
		//cout << Val << endl;
		//Val = newVal;
	}
}

When set() is instantiated with a first parameter that contain a MyContainer of basic type let's say int, then T is int and the expressions 

Quote

//cout << Val[ID] << endl;
        //Val[ID] = newVal;

becomes totally invalid (even though the control flow would never send us there).

On the other hand when I instantiate it with a MyContainer<vector<int>> as first parameter, then the expressions  

Quote

//cout << Val << endl;
 //Val = newVal;

make no sense at all. (cout of vector<int> and assignment of int to vector<int>

Working with templates can be confusing, it seems :P

How is this situation handled? Should Val be passed down to a function template with partial specialization for vectors and non vectors? I think that would work, but can't be too sure with this stuff :P

 

Function overloading did it, so that must be the way to go, (I hope)

This is actually cool, because I can direct the flow of things toward the no inplicit conversion function overload! :D 

I'm discovering hot water here xD


template <typename T, typename V>
void set(MyContainer<T>& e, V newVal, size_t ID = 0)
{
	cout << "MyContainer specialization called" << endl;
	T& Val = e.get();
	set(Val, newVal, ID);
}

template <typename T>
void set(vector<T>& Val, T newVal, size_t ID = 0)
{
	cout << "vector specialization called" << endl;
	Val[ID] = newVal;
}
template <typename T, typename V>
void set(vector<T>& Val, V newVal, size_t ID = 0)
{
	cout << "NO IMPLICIT CONVERSION called" << endl;
}


template <typename T, typename V>
void set(T& Val, V newVal, size_t ID = 0)
{
	cout << "base type specialization called" << endl;
	Val = newVal;
}
int main()
{
	MyContainer<int> MySInt(5);
	MyContainer<vector<int>> MySVec(vector<int>(4, 99));

	cout << "MySInt: " << get(MySInt) << endl;
	set(MySInt, 22);
	cout << "MySInt: " << get(MySInt) << endl << endl << endl;

	cout << "MySVec: " << get(MySVec)[2] << endl;
	set(MySVec, 3.3, 2);
	cout << "MySVec: " << get(MySVec)[2] << endl << endl << endl;

	cout << "MySVec: " << get(MySVec)[2] << endl;
	set(MySVec, 64, 2);
	cout << "MySVec: " << get(MySVec)[2] << endl << endl << endl;
	return 0;
}

Output:

Quote

MySInt: 5
MyContainer specialization called
base type specialization called
MySInt: 22


MySVec: 99
MyContainer specialization called
NO IMPLICIT CONVERSION called
MySVec: 99


MySVec: 99
MyContainer specialization called
vector specialization called
MySVec: 64

 

3 hours ago, MarcusAseth said:

S(T v) :val{ v }

I would just use the non-braced initializer list:


S(T v) :val(v)

I only use braced initializer list for zero-initializing arrays. Due to type deductions in C++11/14, braced initializer lists can be tricky.

FYI: setting up VS2017 can be cumbersome (you need lots of LMB clicks) for small C++ tests. You can use the Visual C++ compliant single header web compiler: http://webcompiler.cloudapp.net/.

🧙

11 minutes ago, matt77hias said:

I would just use the non-braced initializer list:



S(T v) :val(v)

 

curly braces are the safest way, right?

If in there I'm initializing member variables, then { } prevents me to pass argument that would require an implicit conversion and possible truncation, so I just use those always and I never have to think about it because if I make a mistake, then things stop working right away :P

9 minutes ago, MarcusAseth said:

curly braces are the safest way, right?

If in there I'm initializing member variables, then { } prevents me to pass argument that would require an implicit conversion and possible truncation, so I just use those and I never have to thing about it becuase if I make a mistake, then things stop working immediately :P

Safe? I guess (although in combination with std::initializer_list constructors?).

Obvious? No.


int a[3] = {1};
cout << a[0] << ',' << a[1] << ',' << a[2] << endl; 

 

What do you mean with the "prevention of passing arguments that would require implicit conversion"?

🧙

28 minutes ago, matt77hias said:

Safe? I guess.

Obvious? No.



int a[3] = {1};
cout << a[0] << ',' << a[1] << ',' << a[2] << endl; 

 

Well, honestly, you example above to me seems obvious though :S

an array of 3 elements taking an initializer list, the initializer list to the right sets the first element to 1.

Quote

What do you mean with the "prevention of passing arguments that would require implicit conversion"?

I mean that the code below won't compile because it requires a conversion from double to int


	int a[3] = { 1.4 };
	cout << a[0] << ',' << a[1] << ',' << a[2] << endl;

If you accidentally passed a float variable you would have lost precision without noticing maybe, that's why I think is valuable to prefer it to the more loose ( )

EDIT: I compiled my example just to be sure, apparently it works, I must have got it wrong from the book o_O

Need to check.

EDIT2: ok, the safety I mentioned won't apply when you use it to initialize an array it seems, but it still work on all the future uses, and I think some safety is better than no safety at all (and it comes for free, so...I'll take it!) :D

1 hour ago, MarcusAseth said:

EDIT2: ok, the safety I mentioned won't apply when you use it to initialize an array it seems, but it still work on all the future uses, and I think some safety is better than no safety at all (and it comes for free, so...I'll take it!) 

99% of my non-default/non-move/non-copy constructors are explicit, so except for implicit primitive-to-primitive conversions, I personally prefer calling the constructor straight away with () instead of {} (which calls a std::initializer_list constructor if present) in the constructor initializer list. Furthermore, my compiler uses the highest (reasonable, so not Wall) warning level to notify me of possible losses of precision due to for instance implicit primitive-to-primitive conversions.

🧙

More template stuff.

I have some doubts regarding template, still. This compile and works, and if I understand it right, I need the operator overloads to be templates in case I try to do something like "Number<int>(3) + Number<float>(4.15)" which are two different type.

If I have those operator overloads just taking a plain Number& as argument, then things won't compile. 

And yet, operator>> and operator<< just work fine with a Number&, how does that make sense?!

EDIT: while I was writing this question, I thought that maybe is because inside a particular template instantiation itself, Number is defined to be the unique type for that Number<T>, therefore by saying Number I am actually calling the unique type of that particular instantiation?

If that was the case I could then just have a  return type Number& in the operator overloads instead of return type Number<T>& , can you guys confirm this is the case? :S


template<typename T>
class Number {
	T val;
public:
	//Constructors
	Number() :val{ 0 } {}
	Number(T v) :val{ v } {}

	//Methods
	T& get() { return val; }

	//Operators
	template<typename U>
	Number<T>& operator+(Number<U>& rhs) { val += rhs.get(); return *this; }
	template<typename U>
	Number<T>& operator-(Number<U>& rhs) { val -= rhs.get(); return *this; }
	template<typename U>
	Number<T>& operator*(Number<U>& rhs) { val *= rhs.get(); return *this; }
	template<typename U>
	Number<T>& operator/(Number<U>& rhs) { val /= rhs.get(); return *this; }

	friend ostream& operator<<(ostream& stream, Number& rhs) { stream << rhs.val; return stream; }
	friend istream& operator>>(istream& stream, Number& rhs) { stream >> rhs.val; return stream; }
};

int main()
{
	Number<double> myDouble(3.4);
	myDouble - Number<int>(6.6);
	cout << myDouble << endl << endl;
	
	cin >> myDouble;
	cout << myDouble << endl;

	return 0;
}

 

This topic is closed to new replies.

Advertisement