3
struct P {
  int x, y;

  friend P operator-(P u, P v) { return {u.x - v.x, u.y - v.y}; }

  friend int cross(P u, P v) { return u.x * v.y - u.y * v.x; }
  int cross(P u, P v) const { return cross(u - *this, v - *this); }
};

The method is an infinite loop (it will call itself instead of the friend).

Is there any way to get around this issue without changing the interface (the names)?

elbrunovsky
  • 442
  • 5
  • 11
  • 3
    Why does the friend have to be defined inline? Is it because you want it to be hidden from ordinary name lookup? – Brian Bi Aug 13 '21 at 22:53
  • When I run your code, I get this compile error ```"error: no match for ‘operator-’ (operand types are ‘P’ and ‘const P’)"```. Do you have any compile error ? If not, can you show us your whole program ? – Job_September_2020 Aug 13 '21 at 22:55
  • In the member function, explicitly call the non-member (friend) function. For example, `int cross(P u, P v) {return ::cross(u - *this, v - *this);}`. [Note: I'm assuming that is what you intend the effect to be.]. – Peter Aug 14 '21 at 12:46
  • In this particular case, the friend does not need access to P's internals. But I didn't see a simple way to define the global before (it needs P to be declared) or after (it breaks the method) either :/ @Peter that is what I intend, but the compiler won't find ::cross with just this fix – elbrunovsky Aug 15 '21 at 12:59
  • Simple: *don't call the member function `cross`*. It's confusing as hell and if you ask me to do a code review, it goes down, no ifs or buts. Leave the name `cross` for the friend and invent another name for the member. Better yet, ditch the member altogether and add another friend with 3 arguments. – n. m. could be an AI Aug 15 '21 at 13:16

4 Answers4

2

It is possible with a declaration inside the member function:

#include <stdio.h>

struct Foo {
  friend int add(int a, int b) {
    return a + b;
  }

  int add(int a, int b) const {
    int add(int, int); // function declaration
    return add(a, b);  // call that declared function
  }
};

int main() {
  Foo foo;
  printf("%d", foo.add(1, 2)); // prints 3
}

Demo

Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
  • The linker isn't finding `add(int, int)` in this example, but I'll see if I can tweak this. Also note I need the friend function to take Foo as an argument – elbrunovsky Aug 15 '21 at 13:08
  • 1
    @elbrunovsky use GCC 11.0 or later (or clang 8.0 or later). As you can see from the demo I shared, it works. – Aykhan Hagverdili Aug 15 '21 at 13:10
0

You can't have two member functions with the same name and the same signature in a struct (or class).

One solution is to move the friend function out of the struct, so that it can be referred to with a global namespace specifier ::

#include <iostream>

int cross(int i) { return 42; }

struct P {
    friend int cross(int i);
    int cross(int i) const { return ::cross(i - 1); }
};

int main() {
    P p = {};
    std::cout << p.cross(7) << std::endl;
}

The original code produced two warnings, very descriptive:

1 > 1.cpp(6, 25) : warning C4514 : 'cross' : unreferenced inline function has been removed

1 > 1.cpp(7) : warning C4717 : 'P::cross' : recursive on all control paths, function will cause runtime stack overflow

Re: without changing the interface (the names) - you could simply rename your friend function and even make it private; it is not a part of the interface.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Vlad Feinstein
  • 10,960
  • 1
  • 12
  • 27
  • You omitted a problem of having `P` as parameter to `cross()`, but this may be solved by changing parameter type to (const) reference and defining the global `cross()` below `P` (and adding a couple of forward declarations). [See it online](https://wandbox.org/permlink/U1rJE4inGbKYgEtY) – Yksisarvinen Aug 13 '21 at 23:08
0

Ayxan Haqverdili's answer seems to be cleanest possible, and abides by the ISO. However, pre-gcc 11.0 and pre-clang 8.0 appear to have bugs that cause this to fail.

Luckily in this case there's an alternative that uses argument-dependent lookup:

#include <iostream>

class Foo
{
    friend int Bar (Foo)
    {
        return 13;
    }
    friend int RedirectToBar (Foo);
    
public:

    int Bar (Foo)
    {
        return RedirectToBar(Foo());
    }
};

int RedirectToBar (Foo)
{
    return Bar(Foo());
}

int main ()
{
    std::cout << Foo().Bar(Foo()) << std::endl;
}

Live demo

Keep in mind that this only works because some of the arguments to the function are an associated class.

If that's not the case and you really want to keep this design and really need compatibility with older compilers, then we can add in a dummy associated class without breaking the interface by adding it as a default parameter:

#include <iostream>

int RedirectToBar ();

class Foo
{
    struct Key {};

    friend int Bar (Key = Key())
    {
        return 13;
    }
    friend int RedirectToBar ();
    
public:

    int Bar ()
    {
        return RedirectToBar();
    }
};

int RedirectToBar ()
{
    return Bar(Foo::Key());
}

int main ()
{
    std::cout << Foo().Bar() << std::endl;
}

Live demo

However, it's important to add that all of this is a big code smell. Although I don't know the real-world problem of the OP, this seems like a terrible hack to get around bad design choices.

Elliott
  • 2,603
  • 2
  • 18
  • 35
0

I ended up using this solution similar to Peter's and Yksisarvinen's:

struct P {
    int x, y;

    friend P operator-(P u, P v) { return P{u.x - v.x, u.y - v.y}; }

    friend int cross(P u, P v);
    int cross(P u, P v) const;
};

int cross(P u, P v) { return u.x * v.y - u.y * v.x; }
int P::cross(P u, P v) const { return ::cross(u - *this, v - *this); }

Not quite sure yet why the compiler doesn't find ::cross if I write it inside of P.

Note, there is one potential drawback: we have to specify the return type of both functions (can't have it deduced with auto), but this was not needed in this case.

elbrunovsky
  • 442
  • 5
  • 11