简体   繁体   中英

Why would a struct need a friend function?

I'm trying to build a program whose source I downloaded from the internet. When I try to compile it, I get the error message

friend declaration specifying a default argument must be the only declaration

Here is the offending code:

typedef int Var;
struct Lit {
    int     x;
    // Use this as a constructor:
    friend Lit mkLit(Var var, bool sign = false);
    bool operator == (Lit p) const { return x == p.x; }
    bool operator != (Lit p) const { return x != p.x; }
    bool operator <  (Lit p) const { return x < p.x;  } 
inline  Lit  mkLit(Var var, bool sign) { Lit p; p.x = var + var + (int)sign; return p; }

I've found several questions that address this issue, but the matter is rather abstruse, and I don't really understand the explanations. The answer to this question indicates that I can fix matters by moving the inline defintion of mkLit before the struct, removing the default argument from the friend function declaration, and moving it to the inline definition. Is this correct?

But more basically, I don't understand why a struct needs a friend function, since its members are public anyway. The accepted answer to this question gives an answer (that my ignorance prevents me from understanding) in terms of argument dependent lookup, but I can't see that it applies in this case.

Is there any drawback to just deleting the friend function declaration, and moving the default argument to the inline function definition? If so, can you give me a simple example?

But more basically, I don't understand why a struct needs a friend function, since its members are public anyway.

This is a misunderstanding. There are no structs and classes in C++, but C++ only has classes that can be declared with one of the keywords struct or class . The only difference is the default access, ie the following two are identical (apart from the order of their members, which matters if you take their address):

struct foo : private bar {
    int x;
private:
    int y;
};

And the same with class :

class foo : bar {    
    int y;
public:    
    int x;
};

Using class or struct to declare a class is purely a matter of convention. Hence, your question translates to "Why would a class need a friend function?" and the answer is: To allow the friend to access private fields.

The question you linked is about defining the friend function inline vs just declaring it, ie

struct foo { 
    friend void foofriend() { /*put implementation here*/ }
};

vs

struct foo {
    friend void foofriend();
};

void foofriend() { /*put implementation here*/ }

This is indeed related to ADL (tbh I also could not explain it) and is kind of orthogonal to the question what friends are good for.

It took a while until I got it but finally I found out.

Thereby, I ignore the mis-leading title (which has already been answered by user463035818 IMHO sufficiently) and concentrate on

friend declaration specifying a default argument must be the only declaration

which raised my attention.

Therefore, I don't repeat what has been said about friend and access to public members because I think this error deals with a different issue.

First I tried example of OP on coliru (with a little fix and a main() to test):

#include <iostream>

typedef int Var;
struct Lit {
    int     x;
    // Use this as a constructor:
    friend Lit mkLit(Var var, bool sign = false);
    bool operator == (Lit p) const { return x == p.x; }
    bool operator != (Lit p) const { return x != p.x; }
    bool operator <  (Lit p) const { return x < p.x;  }
};
inline  Lit  mkLit(Var var, bool sign) { Lit p; p.x = var + var + (int)sign; return p; }

int main()
{
  Lit lit2 = mkLit(123, false);
  std::cout << "lit2.x: " << lit2.x << '\n';
  return 0;
}

Output:

g++ (GCC) 8.1.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

lit2.x: 246

Live Demo on coliru

Ah, yepp. Runs fine.

Then I did the same on wandbox this time with clang HEAD 8.0.0 :

Start

prog.cc:7:16: error: friend declaration specifying a default argument must be a definition
    friend Lit mkLit(Var var, bool sign = false);
               ^
prog.cc:12:14: error: friend declaration specifying a default argument must be the only declaration
inline  Lit  mkLit(Var var, bool sign) { Lit p; p.x = var + var + (int)sign; return p; }
             ^
prog.cc:7:16: note: previous declaration is here
    friend Lit mkLit(Var var, bool sign = false);
               ^
2 errors generated.

1

Finish

Live Demo on wandbox

Here we are.

So, I tried to understand what clang complains about.

Finally, I found that clang finds Lit Lit::mkLit() and this does not match Lit mkLit() defined later.

This can be fixed by a prototype for mkLit() inserted before struct Lit :

typedef int Var;

Lit mkLit(Var var, bool sign = false);

struct Lit {
    int     x;
    // Use this as a constructor:
    friend Lit mkLit(Var var, bool sign);
    bool operator == (Lit p) const { return x == p.x; }
    bool operator != (Lit p) const { return x != p.x; }
    bool operator <  (Lit p) const { return x < p.x;  }
};

Now, I get a new issue: Lit not (yet) known when proto defined for mkLit() . So, I need a forward declaration for Lit and end up with:

#include <iostream>

typedef int Var;

struct Lit;
Lit mkLit(Var var, bool sign = false);

struct Lit {
    int     x;
    // Use this as a constructor:
    friend Lit mkLit(Var var, bool sign);
    bool operator == (Lit p) const { return x == p.x; }
    bool operator != (Lit p) const { return x != p.x; }
    bool operator <  (Lit p) const { return x < p.x;  }
};
inline  Lit  mkLit(Var var, bool sign) { Lit p; p.x = var + var + (int)sign; return p; }

int main()
{
  Lit lit2 = mkLit(123, false);
  std::cout << "lit2.x: " << lit2.x << '\n';
  return 0;
}

Output:

Start

lit2.x: 246

0

Finish

Live Demo on wandbox

Problem fixed.

I must admit that I'm not sure whether g++ accepts the original version although it shouldn't or whether clang denies the original version although it shouldn't. My stomache feeling tends to the former (ie clang is correct)...


After thinking again what happens in g++ , I came to the conclusion that it does the following:

  1. accept the friend Lit Lit::mkLit() (which is never used)

  2. defining another Lit mkLit() .

To find out I "converted" struct Lit to class Lit and this brings an error in g++ as well:

#include <iostream>

typedef int Var;
class Lit {
    int     x;
    // Use this as a constructor:
    friend Lit mkLit(Var var, bool sign = false);
    bool operator == (Lit p) const { return x == p.x; }
    bool operator != (Lit p) const { return x != p.x; }
    bool operator <  (Lit p) const { return x < p.x;  }
};
inline  Lit  mkLit(Var var, bool sign) { Lit p; p.x = var + var + (int)sign; return p; }

int main()
{
  Lit lit2 = mkLit(123, false);
  std::cout << "lit2.x: " << lit2.x << '\n';
  return 0;
}

Output:

g++ (GCC) 8.1.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

main.cpp: In function 'int main()':
main.cpp:17:35: error: 'int Lit::x' is private within this context
   std::cout << "lit2.x: " << lit2.x << '\n';

Live Demo on coliru

So, the original version of OP just worked in g++ because struct Lit with public int Lit::x simply doesn't need the friend Lit::mkLit() .

Now, I'm a bit puzzled. Which one is right g++ or clang ? I don't know.

As many have mentioned before, a struct is the same as a class , except that its members are public by default. It can have private members.

I'm guessing here, but I think the idea behind making this a friend factory function is that it the designer of the library is reserving the right to initialize any private members added in the future.

To answer your questions:

  • Moving the default parameter to the inline function seems to work.
  • Moving the mkLit before the struct is a problem because it returns a Lit , and Lit hasn't been defined yet (since it now appears below mkLit ).
  • Removing the friend declaration inside the struct will work because the struct has no private members -- for now. If you ever merge in a newer version of the original library that changes adds private members (or makes members private by default by changing the text struct to class ), then it will stop working.

(note: I pasted your code into http://godbolt.org and tried the various permutations, using different compilers)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM