Templates with classes in c++ [closed] - c++

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
I define on main 2 variables m,n from new class S.
Then, I want to swap them with the template function swap..
The question is: what are the methods that S use to run this code and how it look like? :
template <class T>
void swap(T &a ,T &b)
{
T tmp= a;
a=b;
b=tmp;
}
template <class T>
class S{
public:
S:();
S:(const S& data);
~S();
S &operator=(const S&g);
};
int main(){
S m,n;
swap(m,n);
cout<< "m is "<<m<< "n is "<<n<<endl;
return 0;
}

template <class T>
class S
...
This code means that S is a class template. That is, S is not a class but a template.
S m,n;
This declares two variables of class S. Here is your problem: the syntax requires a class, and you gave it a template.
Solution: "instantiate" your template. To do it, append the template parameter to the name of the template:
S<int> m,n;
Another solution: get rid of the template:
// Removed the line "template <class T>" here
class S {
...
}
...
S m,n;
If you don't know which solution to use, use the second one, because it simplifies your code.

You don't need to implement your own class for your template function example, because you very certainly just want to swap numbers.
However if you decide to make your own (whatever) template class, then you may have to overload the assignment operator and maybe define a proper constructor to use your swap function properly. For the standard output in your example you also have to overload this operator http://www.cplusplus.com/reference/ostream/ostream/operator%3C%3C/.
Unfortunately the other features you have to implement (and how) depend on what your (whatever) class should do.. Your question is not very certain in that.

This is a good article from cplusplus.com. Note that, A& and const A& are two different types. Therefore, your swap function would only use the copy constructor ( assignment operator) of the parameter type A&, not const A&. Play with the following code.
template <class T>
void myswap(T &a, T &b) {
T temp = a;
a = b;
b = temp;
}
template <class T>
class A {
public:
T t;
A( T t_ ) : t(t_){}
A( A& a){
cout<<"In A's copy ctor A&a"<<endl;
t = a.t;
}
A& operator = (A& a){
cout<<"In A's assignment operator A&a"<<endl;
t = a.t;
}
};
int main() {
A<int> a1(3);
A<int> a2(4);
myswap(a1, a2);
cout<<a1.t << " " << a2.t <<endl;
return 0;
}
The output is
In A's copy ctor A&a
In A's assignment operator A&a
In A's assignment operator A&a
4 3

Related

Problem with operator+ of templated inherited class [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed last month.
Improve this question
I am trying to figure a problem with a piece of code that I wrote.
I have two structs (bar and foo) that can be used to contruct a templated class myClass.
For this example, the structs will contain information about the size of a vector of a templated quantity (called baseType).
There is also a third struct called infoClass which will contain some information about the object, e.g., its name.
The class myClass will inherite from infoClass and from either bar or foo.
When I write the operator+ for myClass, the compiler is allowing me to do:
myClass<double, foo> = myClass<double, bar> + myClass<double, bar>;
I was able to track the problem to the constructor myClass (const infoClass& obj).
If I put an extra parameter I will get the error I am expecting saying:
error: conversion from ‘myClass<[...],bar>’ to non-scalar type ‘myClass<[...],foo>’ requested
However, I am not understanding why do I need to put an extra parameter in the constructor for the code to work as expected.
Can anyone shed some light on this?
I have the following code:
#include <iostream>
#include <vector>
struct bar
{
int size{10};
};
struct foo
{
int size{15};
};
struct infoClass
{
std::string name;
};
template<typename baseType, typename myType>
class myClass;
typedef myClass<double, bar> doubleBar;
typedef myClass<double, foo> doubleFoo;
template<typename baseType, typename myType>
class myClass
:
public infoClass,
public myType
{
private:
std::vector<baseType> data_;
public:
myClass(double initValue) : data_(this->size,{initValue}){}
myClass (const infoClass& obj) // myClass (const infoClass& obj, double someVar) //this works
:
data_(this->size,{0})
{
this->name = obj.name;
}
std::vector<baseType>& data() {return data_;}
const std::vector<baseType>& data() const {return data_;}
};
template<typename baseType, typename myType>
myClass<baseType, myType> operator+(const myClass<baseType, myType>& obj1, const myClass<baseType, myType>& obj2)
{
if(obj1.data().size() != obj2.data().size())
std::cout << "error, sizes are different" << std::endl;
myClass<baseType, myType> result(0);
for(int i=0; i < obj1.data().size(); i++)
{
result.data()[i] = obj1.data()[i] + obj2.data()[i];
}
return result;
}
int main()
{
doubleBar a(3);
doubleBar b(4);
doubleBar c = a + b;
doubleFoo e = a + a;
std::cout << "End" << std::endl;
return 0;
}
You problem is indeed in myClass (const infoClass& obj) constructor. Such constructor is called "converting construcor" and can be used by compiler to implicitly convert one type to another.
Since both of you types doubleBar and doubleFoo can be implicitly converted to infoClass comlpiler then can convert it back to either of these classes. To prevent this implicit behavior just add keyword explicit in this constructor declaration, i.e.:
explicit myClass (const infoClass& obj)
This will tell compiler not to use this constructor for implicit conversion and everything will work as you expect.

Why template class assignment operator can use "templated copy constructor" to assign different type

I have a template<class T> class Container {}.
While doing some code experiments, I realised that when I call the assigment operator (operator=()) with a different type (i.e. passing a different template parameter to my Container template class), it compiles.
It turns out that this is possible because I also have a "templated copy constructor" (I'm not sure what would be the proper name for this) which is called whenever I call the operator=() with a different argument type.
#include <iostream>
template<typename T>
class Container {
public:
Container() : data() { }
Container(const Container &c) : data(c.data) {
std::cout << "COPY CONSTRUCTOR" << std::endl;
}
// This is what I call a "templated copy constructor".
// If I remove this, the operator=() does not compile with a different type
template<class U>
Container(const Container<U> &c) : data(c.getData()) {
std::cout << "TEMPLATE COPY CONSTRUCTOR??" << std::endl;
}
Container &operator=(const Container &c) {
std::cout << "assignment operator" << std::endl;
if (this == &c)
return *this;
this->data = c.getData();
return *this;
}
const T &getData() const {
return this->data;
}
private:
T data;
};
int main() {
Container<int> c1;
Container<float> c2;
c2 = c1; // Assigning a Container<int> to a Container<float>
return 0;
}
The code above compiles without any errors. If I remove the "templated copy constructor" the compiler gives me this error:
test.cpp:41:5: error: no viable overloaded '='
c3 = c1;
~~ ^ ~~
test.cpp:18:14: note: candidate function not viable: no known conversion from 'Container<int>' to 'const Container<float>' for 1st argument
Container &operator=(const Container &c) {
Can someone explain why this happens and what exactly does the "templated copy constructor" do? Thanks in advance! :)
Just like #Jarod42 said in the comments. I used cppinsights.io and realised that the compiler is seeing c2 = c1 as c2.operator=(Container<float>(c1));, so I suppose it is simply looking for a conversion constructor (what we called a "templated copy constructor" earlier), to see if there is any known way to cast one type to the other.
Your operator= only accepts a Container<T>, which in the case of c3 is a Container<int>.
Something needs to convert c1 from a Container<float> to a Container<int>. The copy constructor is doing that.
Well. What exactly does "class" mean? Or in other words, why we need operator overloading for a class?
When we overload an operator, we define how to operate data belonging to the same class. How do we compare them? Let's declare and implement an overloaded operator '<' for it to solve this problem.
class Stone
{
public:
bool operator<(const Stone& anotherStone)
{
return this->_weight < anotherStone._weight;
}
float _weight;
}
You see, the class of stone can then be compared. Similarity, we define the assignment of the same class of things by declaring and implementing '=' operator.
class Stone
{
public:
bool operator<(const Stone& anotherStone)
{
return this->_weight < anotherStone._weight;
}
void operator=(const Strong& anotherStone)
{
this->_weight = anotherStone._weight;
}
float _weight;
}
In conclusion, we overload operators just for manipulating data within the same class, and that's the usage of operator overloading.
For your code, c1 belongs to class Container and c2 belongs to class Container, they are totally different classes. It makes no sense to define a 'template copy constructor'.
But the compilation complains nothing just because the type 'int' and type 'float' could be converted one another implicitly. If the variable c2 is the type of Container, you will not be lucky like this.

Implicitly cast wrapper class to supperclass in templated call

In designing a DSL (which compiles into C++), I found it convenient to define a wrapper class that, uppon destruction, would call a .free() method on the contained class:
template<class T>
class freeOnDestroy : public T {
using T::T;
public:
operator T&() const { return *this; }
~freeOnDestroy() { T::free(); }
};
The wrapper is designed to be completely transparent: All methods, overloads and constructors are inherited from T (at least to my knowledge), but when included in the wrapper, the free() method is called uppon destruction. Note that I explicitly avoid using T's destructor for this since T::free() and ~T() may have different semantics!
All this works fine, untill a wrapped class gets used as a member to a non-reference templated call, at which point freeOnDestroy is instantiated, calling free on the wrapped object. What I would like to happen is for the tempated method to use T instead of freeOnDestroy<T>, and to implicitly cast the parameter into the supperclass. The following code sample illustrates this problem:
// First class that has a free (and will be used in foo)
class C{
int * arr;
public:
C(int size){
arr = new int[size];
for (int i = 0; i < size; i++) arr[i] = i;
}
int operator[] (int idx) { return arr[idx]; }
void free(){ cout << "free called!\n"; delete []arr; }
};
// Second class that has a free (and is also used in foo)
class V{
int cval;
public:
V(int cval) : cval(cval) {}
int operator[] (int idx) { return cval; }
void free(){}
};
// Foo: in this case, accepts anything with operator[int]
// Foo cannot be assumed to be written as T &in!
// Foo in actuality may have many differently-templated parameters, not just one
template<typename T>
void foo(T in){
for(int i = 0; i < 5; i++) cout << in[i] << ' ';
cout << '\n';
}
int main(void){
C c(15);
V v(1);
freeOnDestroy<C> f_c(15);
foo(c); // OK!
foo(v); // OK!
foo<C>(f_c); // OK, but the base (C) of f_c may not be explicitly known at the call site, for example, if f_c is itself received as a template
foo(f_c); // BAD: Creates a new freeOnDestroy<C> by implicit copy constructor, and uppon completion calls C::free, deleting arr! Would prefer it call foo<C>
foo(f_c); // OH NO! Tries to print arr, but it has been deleted by previous call! Segmentation fault :(
return 0;
}
A few non solutions I should mention are:
Making freeOnDestroy::freeOnDestroy(const freeOnDestroy &src) explicit and private, but this seems to override T's constructor. I'd hoped it would try to implicitly convert it to T and use that as the template argument.
Assume foo receives a reference of its templated arguments (as in void foo(T &in): This is neither the case, nor desirable in some cases
Always explicitly template the call to foo, as in foo<C>(f_c): f_c itself may be templated, so it's hard to know to instantiate foo with C (yes, this could be done with creating multiple versions of foo, to remove the wrappers one by one, but I can't find a way of doing that without creating a different overload for each templated argument of foo).
In summary, my question is: Is there a clean(ish) method to ensure a base class will be casted to its superclass when resolving a template? Or, if not, is there some way of using SFINAE, by causing a substitution failure when the template argument is an instance of the wrapper class, and thus force it to use the implicit cast to the wrapped class (without duplicating each foo-like method signature possibly dozens of times)?
I presently have a work-arround that involves changes in the DSL, but I'm not entirely happy with it, and was curious if it was at all possible to design a wrapper class that works as described.
The problem here not when "wrapped class gets used as a member to a non-reference templated call".
The problem here is that the template wrapper -- and likely its superclass too -- has violated the Rule Of Three.
Passing an instance of the class as a non-reference parameter is just another way of saying "passing by value". Passing by value makes a copy of the instance of the class. Neither your template class -- nor its wrapped class, most likely -- has an explicit copy constructor; as such the copied instance of the class has no knowledge that it is a copy, hence the destructor does what it thinks it should do.
The correct solution here is not to hack something up that makes passing an instance of freeOnDestroy<T> by value end up copying T, rather than freeOnDestroy<T>. The correct solution is to add a proper copy-constructor and the assignment operator to both the freeOnDestroy template, and possibly any superclass that uses it, so that everything complies with the Rule Of Three.
You can use a properly defined detector and a sfinaed function, as it follows:
#include<iostream>
#include<type_traits>
template<class T>
class freeOnDestroy : public T {
using T::T;
public:
operator T&() const { return *this; }
~freeOnDestroy() { T::free(); }
};
template<typename T>
struct FreeOnDestroyDetector: std::false_type { };
template<typename T>
struct FreeOnDestroyDetector<freeOnDestroy<T>>: std::true_type { };
class C{
int * arr;
public:
C(int size){
arr = new int[size];
for (int i = 0; i < size; i++) arr[i] = i;
}
int operator[] (int idx) { return arr[idx]; }
void free(){ std::cout << "free called!\n"; delete []arr; }
};
class V{
int cval;
public:
V(int cval) : cval(cval) {}
int operator[] (int idx) { return cval; }
void free(){}
};
template<typename..., typename T>
std::enable_if_t<not FreeOnDestroyDetector<std::decay_t<T>>::value>
foo(T in) {
std::cout << "here you have not a freeOnDestroy based class" << std::endl;
}
template<typename..., typename T>
std::enable_if_t<FreeOnDestroyDetector<std::decay_t<T>>::value>
foo(T &in) {
std::cout << "here you have a freeOnDestroy based class" << std::endl;
}
int main(void){
C c(15);
V v(1);
freeOnDestroy<C> f_c(15);
foo(c);
foo(v);
foo<C>(f_c);
foo(f_c);
foo(f_c);
return 0;
}
As you can see by running the example, free is called only once, that is for the freeOnDestroy created in the main function.
If you want to forbid definitely freeOnDestroy as a parameter, you can use a single function as the following one:
template<typename..., typename T>
void foo(T &in) {
static_assert(not FreeOnDestroyDetector<std::decay_t<T>>::value, "!");
std::cout << "here you have a freeOnDestroy based class" << std::endl;
}
Note that I added a variadic parameter as a guard, so that one can no longer use foo<C>(f_c); to force a type to be used.
Remove it if you want to allow such an expression. It was not clear from the question.
One solution, which, although a little ugly, seems to work, is to use an overloaded unwrapping method, such as:
template<typename T> T freeOnDestroyUnwrapper(const T &in){ return in; }
template<typename T> T freeOnDestroyUnwrapper(const freeOnDestroy<T> &in){ return in; }
template<typename T> T freeOnDestroyUnwrapper(const freeOnDestroy<typename std::decay<T>::type> &in){ return in; }
template<typename T> T& freeOnDestroyUnwrapper(T &in){ return in; }
template<typename T> T& freeOnDestroyUnwrapper(freeOnDestroy<T> &in){ return in; }
template<typename T> T& freeOnDestroyUnwrapper(freeOnDestroy<typename std::decay<T>::type> &in){ return in; }
Then, calls can be made using the unwrapper:
int main(void){
C c(15);
V v(1);
freeOnDestroy<C> f_c(15);
foo(freeOnDestroyUnwrapper(c));
foo(freeOnDestroyUnwrapper(v));
foo<C>(freeOnDestroyUnwrapper(f_c));
foo(freeOnDestroyUnwrapper(f_c));
foo(freeOnDestroyUnwrapper(f_c));
return 0;
}
Or, to make this less verbose, we can alter foo so it does this for us:
template<typename T>
void _foo(T in){
for(int i = 0; i < 5; i++) cout << in[i] << ' ';
cout << '\n';
}
template<typename... Ts>
void foo(Ts&&... args){
_foo(freeOnDestroyUnwrapper(args)...);
}
And then call it as normal:
int main(void){
C c(15);
V v(1);
freeOnDestroy<C> f_c(15);
foo(c);
foo(v);
//foo<C>(f_c); // This now doesn't work!
foo(f_c);
foo(f_c);
return 0;
}
This seems to work for any number of arguments foo may have (of different templates, if needed), and seems to behave appropriately when foos input is a reference (which does not occur in my context, but would be good for the sake of making this solution generic).
I'm not convinced that this is the best solution, or that it generalizes to every case, plus, having to double all declarations is a bit cumbersome, and opaque to most IDEs autocomplete features. Better solutions and improvements are welcome!

Use of rvalue references in function parameter of overloaded function creates too many combinations

Imagine you have a number of overloaded methods that (before C++11) looked like this:
class MyClass {
public:
void f(const MyBigType& a, int id);
void f(const MyBigType& a, string name);
void f(const MyBigType& a, int b, int c, int d);
// ...
};
This function makes a copy of a (MyBigType), so I want to add an optimization by providing a version of f that moves a instead of copying it.
My problem is that now the number of f overloads will duplicate:
class MyClass {
public:
void f(const MyBigType& a, int id);
void f(const MyBigType& a, string name);
void f(const MyBigType& a, int b, int c, int d);
// ...
void f(MyBigType&& a, int id);
void f(MyBigType&& a, string name);
void f(MyBigType&& a, int b, int c, int d);
// ...
};
If I had more parameters that could be moved, it would be unpractical to provide all the overloads.
Has anyone dealt with this issue? Is there a good solution/pattern to solve this problem?
Thanks!
Herb Sutter talks about something similar in a cppcon talk
This can be done but probably shouldn't. You can get the effect out using universal references and templates, but you want to constrain the type to MyBigType and things that are implicitly convertible to MyBigType. With some tmp tricks, you can do this:
class MyClass {
public:
template <typename T>
typename std::enable_if<std::is_convertible<T, MyBigType>::value, void>::type
f(T&& a, int id);
};
The only template parameter will match against the actual type of the parameter, the enable_if return type disallows incompatible types. I'll take it apart piece by piece
std::is_convertible<T, MyBigType>::value
This compile time expression will evaluate to true if T can be converted implicitly to a MyBigType. For example, if MyBigType were a std::string and T were a char* the expression would be true, but if T were an int it would be false.
typename std::enable_if<..., void>::type // where the ... is the above
this expression will result in void in the case that the is_convertible expression is true. When it's false, the expression will be malformed, so the template will be thrown out.
Inside the body of the function you'll need to use perfect forwarding, if you are planning on copy assigning or move assigning, the body would be something like
{
this->a_ = std::forward<T>(a);
}
Here's a coliru live example with a using MyBigType = std::string. As Herb says, this function can't be virtual and must be implemented in the header. The error messages you get from calling with a wrong type will be pretty rough compared to the non-templated overloads.
Thanks to Barry's comment for this suggestion, to reduce repetition, it's probably a good idea to create a template alias for the SFINAE mechanism. If you declare in your class
template <typename T>
using EnableIfIsMyBigType = typename std::enable_if<std::is_convertible<T, MyBigType>::value, void>::type;
then you could reduce the declarations to
template <typename T>
EnableIfIsMyBigType<T>
f(T&& a, int id);
However, this assumes all of your overloads have a void return type. If the return type differs you could use a two-argument alias instead
template <typename T, typename R>
using EnableIfIsMyBigType = typename std::enable_if<std::is_convertible<T, MyBigType>::value,R>::type;
Then declare with the return type specified
template <typename T>
EnableIfIsMyBigType<T, void> // void is the return type
f(T&& a, int id);
The slightly slower option is to take the argument by value. If you do
class MyClass {
public:
void f(MyBigType a, int id) {
this->a_ = std::move(a); // move assignment
}
};
In the case where f is passed an lvalue, it will copy construct a from its argument, then move assign it into this->a_. In the case that f is passed an rvalue, it will move construct a from the argument and then move assign. A live example of this behavior is here. Note that I use -fno-elide-constructors, without that flag, the rvalue cases elides the move construction and only the move assignment takes place.
If the object is expensive to move (std::array for example) this approach will be noticeably slower than the super-optimized first version. Also, consider watching this part of Herb's talk that Chris Drew links to in the comments to understand when it could be slower than using references. If you have a copy of Effective Modern C++ by Scott Meyers, he discusses the ups and downs in item 41.
You may do something like the following.
class MyClass {
public:
void f(MyBigType a, int id) { this->a = std::move(a); /*...*/ }
void f(MyBigType a, string name);
void f(MyBigType a, int b, int c, int d);
// ...
};
You just have an extra move (which may be optimized).
My first thought is that you should change the parameters to pass by value. This covers the existing need to copy, except the copy happens at the call point rather than explicitly in the function. It also allows the parameters to be created by move construction in a move-able context (either unnamed temporaries or by using std::move).
Why you would do that
These extra overloads only make sense, if modifying the function paramers in the implementation of the function really gives you a signigicant performance gain (or some kind of guarantee). This is hardly ever the case except for the case of constructors or assignment operators. Therefore, I would advise you to rethink, whether putting these overloads there is really necessary.
If the implementations are almost identical...
From my experience this modification is simply passing the parameter to another function wrapped in std::move() and the rest of the function is identical to the const & version. In that case you might turn your function into a template of this kind:
template <typename T> void f(T && a, int id);
Then in the function implementation you just replace the std::move(a) operation with std::forward<T>(a) and it should work. You can constrain the parameter type T with std::enable_if, if you like.
In the const ref case: Don't create a temporary, just to to modify it
If in the case of constant references you create a copy of your parameter and then continue the same way the move version works, then you may as well just pass the parameter by value and use the same implementation you used for the move version.
void f( MyBigData a, int id );
This will usually give you the same performance in both cases and you only need one overload and implementation. Lots of plusses!
Significantly different implementations
In case the two implementations differ significantly, there is no generic solution as far as I know. And I believe there can be none. This is also the only case, where doing this really makes sense, if profiling the performance shows you adequate improvements.
You might introduce a mutable object:
#include <memory>
#include <type_traits>
// Mutable
// =======
template <typename T>
class Mutable
{
public:
Mutable(const T& value) : m_ptr(new(m_storage) T(value)) {}
Mutable(T& value) : m_ptr(&value) {}
Mutable(T&& value) : m_ptr(new(m_storage) T(std::move(value))) {}
~Mutable() {
auto storage = reinterpret_cast<T*>(m_storage);
if(m_ptr == storage)
m_ptr->~T();
}
Mutable(const Mutable&) = delete;
Mutable& operator = (const Mutable&) = delete;
const T* operator -> () const { return m_ptr; }
T* operator -> () { return m_ptr; }
const T& operator * () const { return *m_ptr; }
T& operator * () { return *m_ptr; }
private:
T* m_ptr;
char m_storage[sizeof(T)];
};
// Usage
// =====
#include <iostream>
struct X
{
int value = 0;
X() { std::cout << "default\n"; }
X(const X&) { std::cout << "copy\n"; }
X(X&&) { std::cout << "move\n"; }
X& operator = (const X&) { std::cout << "assign copy\n"; return *this; }
X& operator = (X&&) { std::cout << "assign move\n"; return *this; }
~X() { std::cout << "destruct " << value << "\n"; }
};
X make_x() { return X(); }
void fn(Mutable<X>&& x) {
x->value = 1;
}
int main()
{
const X x0;
std::cout << "0:\n";
fn(x0);
std::cout << "1:\n";
X x1;
fn(x1);
std::cout << "2:\n";
fn(make_x());
std::cout << "End\n";
}
This is the critical part of the question:
This function makes a copy of a (MyBigType),
Unfortunately, it is a little ambiguous. We would like to know what is the ultimate target of the data in the parameter. Is it:
1) to be assigned to an object that existing before f was called?
2) or instead, stored in a local variable:
i.e:
void f(??? a, int id) {
this->x = ??? a ???;
...
}
or
void f(??? a, int id) {
MyBigType a_copy = ??? a ???;
...
}
Sometimes, the first version (the assignment) can be done without any copies or moves. If this->x is already long string, and if a is short, then it can efficiently reuse the existing capacity. No copy-construction, and no moves. In short, sometimes assignment can be faster because we can skip the copy contruction.
Anyway, here goes:
template<typename T>
void f(T&& a, int id) {
this->x = std::forward<T>(a); // is assigning
MyBigType local = std::forward<T>(a); // if move/copy constructing
}
If the move version will provide any optimization then the implementation of the move overloaded function and the copy one must be really different. I don't see a way to get around this without providing implementations for both.

C++ adding friend to a template class in order to typecast

I'm currently reading "Effective C++" and there is a chapter that contains code similiar to this:
template <typename T>
class Num {
public:
Num(int n) { ... }
};
template <typename T>
Num<T> operator*(const Num<T>& lhs, const Num<T>& rhs) { ... }
Num<int> n = 5 * Num<int>(10);
The book says that this won't work (and indeed it doesn't) because you can't expect the compiler to use implicit typecasting to specialize a template.
As a soluting it is suggested to use the "friend" syntax to define the function inside the class.
//It works
template <typename T>
class Num {
public:
Num(int n) { ... }
friend
Num operator*(const Num& lhs, const Num& rhs) { ... }
};
Num<int> n = 5 * Num<int>(10);
And the book suggests to use this friend-declaration thing whenever I need implicit conversion to a template class type. And it all seems to make sense.
But why can't I get the same example working with a common function, not an operator?
template <typename T>
class Num {
public:
Num(int n) { ... }
friend
void doFoo(const Num& lhs) { ... }
};
doFoo(5);
This time the compiler complaints that he can't find any 'doFoo' at all.
And if i declare the doFoo outside the class, i get the reasonable mismatched types error. Seems like the "friend ..." part is just being ignored.
So is there a problem with my understanding? What is the difference between a function and an operator in this case?
The reason is that here
doFoo(5);
the compiler has no way of finding foo, given an int parameter. This would be the equivalent of calling your friend operator like this:
Num<int> n = 5 * 10;
This will "work", but not by calling the friend operator* defined in your Num class, but by calling the built-in operator* for integers, and then using the implicit conversion from Num's converting constructor.
The core problem is lookup. A friend declaration provides a declaration of a namespace level function, but the declaration is only available inside the class that is befriending it. In the example the book provides that is not an issue: the function takes two arguments of the enclosing type, as long as one of them is of the enclosing type, Argument Dependent Lookup will look inside the definition of the class and find the operator. In your case that is not the case, since there is a single argument and that needs a conversion, the compiler will not look inside the definition of the class.
Note that this is regardless of templates and conversions:
class A {
friend void f( int ) {}
friend void g( int, A ) {}
};
int main() {
f(5); // Error: lookup cannot find 'f' declared *only* inside A
g(5,A()); // Ok, one argument is 'A', lookup will find the function
}
In the case above, where there are no templates involved, you could potentially add a declaration at namespace level to fix it, but that is not really an option for template classes.
class A {
friend void f() { std::cout << "inside A\n"; }
};
void f(int); // only declaration
int main() {
f(5); // "inside A"
}
This cannot be done for a template (and for all instantiating types) as the friend declaration is a declaration of a non-templated function. Although you could can play with the code just for the sake of testing:
template <typename T>
struct Num {
Num(int x) ...
friend void f( Num const & );
};
Num<int> f(Num<int> const &); // only declaration
int main() {
f(5);
}
Yes these code compiler do not know how to work with it .
like
doFoo(5)
compiler do not know 5 is int