6

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

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 2
    better include the error message in the question. And the question would be more clear if you post the code that does cause the errors (and then explain what to change to make it work) – 463035818_is_not_an_ai May 19 '21 at 21:11
  • 2
    In your member `operator*`, shouldn't `return Example(m_data * other.m_data);` be `return m_data * other;` to be consistent with your non-member `operator*`? The order of the operands shouldn't affect the result – Remy Lebeau May 19 '21 at 21:16
  • It should be: `template friend Example operator*(U, Example);` this fixes issue, but I wonder why first version compiles second doesn't (on all major compilers). – Marek R May 19 '21 at 21:21
  • For the sample instantiation posted the conversion constructor is used in both instances. So it is at best ambiguous. The error messages doesn't make much sense though. The keyword `explicit` should probably help to diagnose this. – Captain Giraffe May 19 '21 at 21:32
  • @MarekR I tried to change the declaration as you suggested but I still get errors if I put the friend declaration underneath the other one. – stack_overflow_nickname May 19 '21 at 21:32
  • https://godbolt.org/z/7jKeGMWK5 – Marek R May 19 '21 at 21:33
  • If I change the order of the two declarations, It still wouldn't work. https://godbolt.org/z/Wr7xqorrb – stack_overflow_nickname May 19 '21 at 21:46
  • @stack_overflow_nickname do you know what `#ifndef ALT` does? Note respective compiler options `-DALT` (`/D ALT`). https://godbolt.org/z/x97sYb94j – Marek R May 19 '21 at 21:49
  • @MarekR No, I've actually never used those before – stack_overflow_nickname May 19 '21 at 21:55
  • It allows alter file content. Now depending if option `-DALT` (`/D ALT`) is used or not specific version of file is used. In your case code it uses code with different order of function declaration (it happens before compilation process). This way both versions are checked with single source file and multiple compilers setup. – Marek R May 19 '21 at 22:10

2 Answers2

5

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...
};
HTNW
  • 27,182
  • 1
  • 32
  • 60
  • `template specialization` needs `template<>` prefix which he does not have! – Marek R May 19 '21 at 22:14
  • 1
    @MarekR [It does not.](https://eel.is/c++draft/class.friend#example-8) You need the `template<>` prefix to declare a specialization of a template, but we're not really doing that. – HTNW May 19 '21 at 22:17
  • you have written about "template specialization" I just quote you, and point out question do not have it. So I agree with you: "we're not really doing that". Also this link of yours do not do that either so I have no idea what was the point. – Marek R May 21 '21 at 06:10
1

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);
}
  • 1
    Is my understanding correct that this will make, for instance, `operator*(double, Example)` a `friend` of `Example`? Is that necessary? (Obviously non-member `operator*` can be written without needing to be a `friend` by changing its implementation to use member `operator*`, but assuming we wanted to keep the `friend` version but make it more parsimonious.) – Nathan Pierson May 19 '21 at 22:11
  • @NathanPierson I believe what you say is right, and maybe that could become inconvenient. If you have any better idea, by all means, do tell. – stack_overflow_nickname May 19 '21 at 22:19
  • 1
    I thought that question was "WHY?", not "how to fix code?". This answer is just my comment under a question. – Marek R May 19 '21 at 22:27
  • The question was why, but finding some useful code seems a bonus. Why wouldn't we share it? – stack_overflow_nickname May 19 '21 at 22:31