Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I have multiple overloads of the * operator in a template class. Why do I get different results when I put the declarations in different order?

I'm having a class which needs to have multiple overloads of the * operator. Some of these need to be declared as friends so I can have the class type as second argument. This is an example of a class which encounters the problem I'm about to present:

#pragma once

template<typename T>
class Example;

template<typename T>
Example<T> operator*(T value, Example<T> obj);

template<typename T>
class Example
{
private:
    T m_data;
public:
    Example(T);
    friend Example operator*<T>(T, Example);
    Example operator*(const T& other);

};

template<typename T>
Example<T> operator*(T value, Example<T> obj)
{
    return value * obj.m_data;
}

template<typename T>
Example<T>::Example(T data) :m_data(data) { }

template<typename T>
 Example<T> Example<T>::operator*(const T& other)
{
    return Example(m_data * other.m_data);
}

This works but If I change:

template<typename T>
class Example
{
private:
    T m_data;
public:
    Example(T);
    friend Example operator*<T>(T, Example);
    Example operator*(const T& other);
};

with

template<typename T>
class Example
{
private:
    T m_data;
public:
    Example(T);
    Example operator*(const T& other);
    friend Example operator*<T>(T, Example);
};

I start getting a bunch of errors even though all I'm doing is swapping those 2 lines containing the declarations of the operator overloads. Can you explain me what is going on here? This makes no sense to me.

Code that generated error:

Example<int> a(2);
2 * a;

Errors:

unexpected token(s) preceding';'  
syntax error missing':' before '<'  
'operator*': redefinition: previous definition was 'function'  
'operator*': looks like a function but there is no parameter list.  
'*': uses 'Example<int>' which is being defined  
'*': friend not permitted on data declarations   

https://godbolt.org/z/j4zYTP8n7

like image 774
stack_overflow_nickname Avatar asked May 19 '21 21:05

stack_overflow_nickname


2 Answers

There are two different things named operator* in your code. There is the function template, which you declare before Example, and the member function of Example. The syntax operator*<T>, a template specialization, is only valid when operator* refers to the template, but not to the member function. In your first declaration, where the friend comes before the member function, the operator* member function has not been declared at the point where the compiler sees operator*<T>, so it resolves the name to the function template declared before Example and everything is fine (the specific specialization operator*<T> becomes a friend of each Example<T>).

template<typename T>
Example<T> operator*(T value, Example<T> obj); // <-\ ...finds a template, so no syntax error
                                               //   |
template<typename T>                           //   |
class Example {                                //   |
    T m_data;                                  //   |
public:                                        //   |
    Example(T);                                //   |
    friend Example operator*<T>(T, Example);   // >-/ looking up this name...
    Example operator*(const T& other);
};

Do it the other way and instead operator*<T> is taken to refer to the member function, which is not a template, and you get a syntax error (specifically, I think it's trying to somehow interpret it as operator* < T > where the < and > are the actual less-than/greater-than operators).

template<typename T>
class Example {
    T m_data;
public:
    Example(T);
    Example operator*(const T& other);       // <-\ ...does not find a template; ouch!
    friend Example operator*<T>(T, Example); // >-/ looking up this name...
};
like image 71
HTNW Avatar answered Sep 28 '22 04:09

HTNW


With your help I've reached the following solution. I was also able to get rid of the redundancy of declarations from the first lines. This is the final product:

template<typename T>
class Example
{
private:
    T m_data;
public:
    Example(T);

    Example operator*(const T& other);

    template<typename T>
    friend Example<T> operator*(T, Example<T>);
};

template<typename T>
Example<T> operator*(T value, Example<T> obj)
{
    return value * obj.m_data;
}

template<typename T>
Example<T>::Example(T data) :m_data(data) { }

template<typename T>
Example<T> Example<T>::operator*(const T& other)
{
    return Example(m_data * other.m_data);
}
like image 42
stack_overflow_nickname Avatar answered Sep 28 '22 03:09

stack_overflow_nickname