Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
dustgun
Jun 20, 2004

And then the doorbell would ring and the next santa would come

litghost posted:

What does it do?
Nothing, that's the beauty of it.

Adbot
ADBOT LOVES YOU

Avenging Dentist
Oct 1, 2005

oh my god is that a circular saw that does not go in my mouth aaaaagh

dustgun posted:

Nothing, that's the beauty of it.

The function-try-block doesn't do anything, but the throw-expression inside the conditional-expression does.

ctz
Feb 6, 2003

litghost posted:

What does it do?

The only thing I'd expect it to do is emit a diagnostic about 'mF' at compilation time.

ShinAli
May 2, 2003

The Kid better watch his step.
hey guys C/stdio question. there is this function:

code:
unsigned char readbyte(FILE *f)
{
  unsigned char d0f_loc;
  FILE * lfi;
  lfi = f;
  fread(&d0f_loc,sizeof(unsigned char),1,lfi);
  return d0f_loc;
}
... which reads one byte from the passed in file, and returns it. Two different FILE pointers are used with this function. The first file is read in and stored into memory, then it's file pointer is closed. The second file is fseek'd to the end, got the file size using ftell, then I did another fseek to the beginning. The file is read intermittently until I get to the end of file.

Or at least that's what supposed to happen. I get a segmentation fault on a machine with Ubuntu on the fread line after it tries to read the 10th byte (refers to iofread.c file in line 43 I think, but the machines didn't have the source for the stdlib they use). I don't get it because it reads in the first file in it's entirety just fine. The weirder thing is it works fine on my Macbook Pro/C2D/OSX-1.5.7.

ShinAli fucked around with this message at 15:19 on Aug 5, 2009

ShoulderDaemon
Oct 9, 2003
support goon fund
Taco Defender

ShinAli posted:

hey guys C/stdio question. there is this function:

code:
unsigned char readbyte(FILE *f)
{
  unsigned char d0f_loc;
  FILE * lfi;
  fread(&d0f_loc,sizeof(unsigned char),1,lfi);
  return d0f_loc;
}
... which reads one byte from the passed in file, and returns it.

No, it doesn't. It completely ignores the passed in stream, attempts to read a single byte from an undefined stream, and returns a byte which may be the result of such a read, but may just be undefined as well because the read isn't checked for success.

ShinAli posted:

Two different FILE pointers are used with this function. The first file is read in and stored into memory,

No, the passed-in file pointer is not referenced. At all.

ShinAli posted:

then it's file pointer is closed.

fclose is not called in that function.

ShinAli posted:

The second file is fseek'd to the end, got the file size using ftell, then I did another fseek to the beginning. The file is read intermittently until I get to the end of file.

What the hell are you talking about?

ShinAli posted:

Or at least that's what supposed to happen. I get a segmentation fault on a machine with Ubuntu on the fread line after it tries to read the 10th byte (refers to iofread.c file in line 43 I think, but the machines didn't have the source for the stdlib they use). I don't get it because it reads in the first file in it's entirety just fine. The weirder thing is it works fine on my Macbook Pro/C2D/OSX-1.5.7.

It is pretty weird that you got this code to work fine anywhere, yes.

ShinAli
May 2, 2003

The Kid better watch his step.

ShoulderDaemon posted:

No, it doesn't. It completely ignores the passed in stream, attempts to read a single byte from an undefined stream, and returns a byte which may be the result of such a read, but may just be undefined as well because the read isn't checked for success.

Sorry, typo. There was supposed to be a lfi = f after FILE * lfi.

As for the rest of your comments, I was trying to express what happens in general in the program and how the function is used with it.

But you can chillax, I figured it out. For some reason this guy doesn't know how to handle his pointers and the FILE pointer got decremented somehow.

ShinAli fucked around with this message at 23:18 on Aug 5, 2009

Contains Acetone
Aug 22, 2004
DROP IN ANY US MAILBOX, POST PAID BY SECUR-A-KEY

Contains Acetone fucked around with this message at 18:10 on Jun 24, 2020

Avenging Dentist
Oct 1, 2005

oh my god is that a circular saw that does not go in my mouth aaaaagh

Contains Acetone posted:

Could someone explain to me why this doesn't work they I would expect it to?

http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.9

(Yes there is a good reason for this, but that would be a short essay, and :effort:. For people playing the home game, consider what would happen if you had Parent::foo(int) and Child::foo(char).)

Avenging Dentist fucked around with this message at 07:13 on Aug 8, 2009

Contains Acetone
Aug 22, 2004
DROP IN ANY US MAILBOX, POST PAID BY SECUR-A-KEY

Contains Acetone fucked around with this message at 18:09 on Jun 24, 2020

Avenging Dentist
Oct 1, 2005

oh my god is that a circular saw that does not go in my mouth aaaaagh

Contains Acetone posted:

It would call foo(int) when passed in int and foo(char) when passed a char :confused:

Sorry, try again!

Contains Acetone
Aug 22, 2004
DROP IN ANY US MAILBOX, POST PAID BY SECUR-A-KEY

Contains Acetone fucked around with this message at 18:09 on Jun 24, 2020

Avenging Dentist
Oct 1, 2005

oh my god is that a circular saw that does not go in my mouth aaaaagh

Contains Acetone posted:

I don't understand. Why on earth does it do that? Why does the compiler think it's a good idea to pass a char to a function that requires an int?

Because a char is an integral type and can be converted to an int using the integer promotion rules defined in the ISO C Standard, §6.3.1.1 and the ISO C++ Standard, §4.5.

Contains Acetone posted:

This situation is different from the one I posted about. I want the parent class to have one method A which uses virtual method B, and then use the child's implemented version of that method B from within the parents method A.

It's not a different situation, it's the exact same situation, but the C++ standard wasn't about to make the subclass overloading rules any more complicated by requiring hiding to occur only in the cases where it was absolutely necessary.

Contains Acetone posted:

Redeclaring the function in the children and calling the base method as suggested in the reference you posted has fixed my problem though.

Actually, I didn't. I (indirectly) suggested using the using syntax.

Contains Acetone posted:

EDIT EDIT: Auto promotion sounds like a bad idea :colbert:

Basically every language with multiple integral types does this because it's a pain in the rear end to write overloads for all of (char, short, int, long, long long) and unsigned variants. In general you should not get mad at the language because it does something you neglected to learn about.

Avenging Dentist fucked around with this message at 07:54 on Aug 8, 2009

Contains Acetone
Aug 22, 2004
DROP IN ANY US MAILBOX, POST PAID BY SECUR-A-KEY

Contains Acetone fucked around with this message at 18:09 on Jun 24, 2020

Avenging Dentist
Oct 1, 2005

oh my god is that a circular saw that does not go in my mouth aaaaagh

Contains Acetone posted:

Can you give me an example of why this is necessary that wouldn't be fixed by simply checking the inputs (ie, not passing a char when an int is required)?

A char is, and always will be, a perfectly acceptable alternative to an int. If you prefer, consider an int and a long.

The issue here is that a virtual member function provides a contract to the programmer that any call to it from the base class will be the same as calling it from the subclass. Overloading in a subclass is a violation of that contract, so C++ expects you to import the base class function into the subclass's namespace as a way of saying "yes I really mean for you to do this".

The hiding rule obviously causes some false positives (non-virtual functions, functions with different arities), but it's better to keep rules as simple as possible, provided they don't allow for dangerous things to happen without the programmer being fully aware. Better to issue a compiler error than to make sure the programmer knows what's going on. Unfortunately, GCC's error reporting is poo poo, and it should be explaining what's going on.

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
I think the argument is pretty weak for instance members, to be honest. Both unexpected overloading and unexpected hiding can cause surprises, but in practice people generally know which names are going to be overloaded. And if you overload functions based on types which are implicitly convertible, well, now you're just asking for it.

Static functions make a more compelling argument, because qualified lookup still finds things from base classes, so it's impossible to say "no seriously, just the one declared right there". It's quite common for, say, factory functions to just append more parameters to the base class's version, like so:

code:
struct Base { static Base* Create(Context*); };
struct Derived : Base {
  static Derived* Create(Context*, foo&);
};

Base* buildit(Context* C, bool useDerived) {
  if (useDerived)
    return Derived::Create(C, C->foo); // would accidentally be Base::Create
  else
    return Base::Create(C);
}
Another argument: there's no way to explicitly hide a base member other than exploiting this trick. And another: broken overrides are marginally more likely to be detected because of this feature. But the static member function problem is the only really compelling one, I think.

Avenging Dentist
Oct 1, 2005

oh my god is that a circular saw that does not go in my mouth aaaaagh

rjmccall posted:

I think the argument is pretty weak for instance members, to be honest. Both unexpected overloading and unexpected hiding can cause surprises, but in practice people generally know which names are going to be overloaded.

Things that work but shouldn't are universally more dangerous than things that don't work but should.

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe

Avenging Dentist posted:

Things that work but shouldn't are universally more dangerous than things that don't work but should.

I agree, but what's your point? Both ways you could decide this question enable "things that work but shouldn't". You yourself just provided an example where hiding methods instead of overloading them caused unwanted behavior with no diagnostic possible.

Avenging Dentist
Oct 1, 2005

oh my god is that a circular saw that does not go in my mouth aaaaagh

rjmccall posted:

You yourself just provided an example where hiding methods instead of overloading them caused unwanted behavior with no diagnostic possible.

Actually my example was kinda lovely, since the "using" is superfluous*. The issue is that without hiding, you end up calling out to strange places based on conversion/default-argument rules, and the standards writers felt it made more sense to isolate lookup of overloads to a single class.

* It does change the behavior when you call foo with an int though.

Avenging Dentist fucked around with this message at 21:54 on Aug 8, 2009

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe

Avenging Dentist posted:

That's because I explicitly imported the base class member function into subclass. Otherwise, you'd absolutely get a diagnostic.

Bah, I'd forgotten which way you had it.

code:
struct Base { void foo(int); }
struct Derived : Base { void foo(double); }
Or any parallel situation where a subclass can handle a "more general" type than the superclass.

UNEDIT: Moving to its own post.

rjmccall fucked around with this message at 21:58 on Aug 8, 2009

Avenging Dentist
Oct 1, 2005

oh my god is that a circular saw that does not go in my mouth aaaaagh

rjmccall posted:

Bah, I'd forgotten which way you had it.

The specifics of my example were stupid, but see above. ^^^

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
I can definitely understand why the committee designed it that way; I just think it was a mistake. Non-static member overload resolution is already a fiendishly complicated and subtle process; hiding doesn't really make it any easier. Hiding does make it easier for humans to reason about in cases where the programmer hasn't explicitly brought base-class operations into scope, but it turns out that that's pretty uncommon for non-static members, because programmers don't generally want subclasses to lose operations from their base classes. So all it adds is more required boilerplate.

Another perspective: suppose you used a rule where all base class instance methods were candidates for instance-method overloading. Would the choices of that rule really be more complicated to explain than, say, the results of argument-dependent lookup?

Avenging Dentist
Oct 1, 2005

oh my god is that a circular saw that does not go in my mouth aaaaagh

rjmccall posted:

I can definitely understand why the committee designed it that way; I just think it was a mistake. Non-static member overload resolution is already a fiendishly complicated and subtle process; hiding doesn't really make it any easier.

(With hiding) it's not really any more complicated than any other kind of overload resolution. One of the more difficult sticking points in a non-hiding subclass overriding system would be the behavior of member functions with default arguments. In terms of overload resolution, they are essentially N+1 separate functions, where N is the number of default arguments. Overriding various subsets of that (but not others) in a subclass would be complicated as poo poo.

rjmccall posted:

Hiding does make it easier for humans to reason about in cases where the programmer hasn't explicitly brought base-class operations into scope, but it turns out that that's pretty uncommon for non-static members, because programmers don't generally want subclasses to lose operations from their base classes.

The subclasses aren't losing any functionality whatsoever. You just need to use qualified-ids to look up hidden base-class functions.

rjmccall posted:

Another perspective: suppose you used a rule where all base class instance methods were candidates for instance-method overloading. Would the choices of that rule really be more complicated to explain than, say, the results of argument-dependent lookup?

Yes because default arguments are a pain even when you're not dealing with selective overriding of subsets of their possible lookup contexts.

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe

Avenging Dentist posted:

Overriding various subsets of that (but not others) in a subclass would be complicated as poo poo.

Only from an implementation perspective, I think, and it's not like this would even make the top five list of C++ Features That Compiler Writers Hate Implementing.

From a user perspective, I don't think it's any different from having k different functions (all with different argument counts) that you wanted to selectively override. Currently, if you want to keep any of the overloads from a base class, you either import all of them into scope and let the overload-resolution mechanism sort it out, or you write explicit proxies for the ones you want to keep. In a no-hiding world, if I wanted to selectively override certain overloads from a base class, I would... write explicit overriding methods, possibly using a method with default arguments. If I didn't want to override a specific (intermediate) default-argument instance, I could write an explicit proxy for it that called the base class's implementation. I don't see how this is inherently more difficult or more complex than what C++ now requires.

Avenging Dentist posted:

The subclasses aren't losing any functionality whatsoever. You just need to use qualified-ids to look up hidden base-class functions.

Weren't you just saying that implicit overloading would change a virtual method's contract by modifying behavior under substitution? So does taking entire methods out of default consideration. I mean, yes, the functionality isn't lost — I could explicitly qualify the method name, or I could cast the implicit argument to the appropriate base, or I could add a proxy, or I could use ghetto overloading and encode parameter types in the method name to avoid the entire issue. But the problem remains that programmers can break working code by adding overloads in subclasses, which no-one expects who hasn't deeply internalized the C++ standard.

Avenging Dentist posted:

Yes because default arguments are a pain even when you're not dealing with selective overriding of subsets of their possible lookup contexts.

A well-designed default arguments implementation would not be seriously challenged by any of this.

Avenging Dentist
Oct 1, 2005

oh my god is that a circular saw that does not go in my mouth aaaaagh

rjmccall posted:

But the problem remains that programmers can break working code by adding overloads in subclasses, which no-one expects who hasn't deeply internalized the C++ standard.

Neither solution is clearly "right" in all cases. The standards writers chose the one that gives you the most flexibility (don't do this by default, but you can do it explicitly if you want), which is pretty much in keeping with C++'s philosophy. This also has nothing to do with knowledge of the C++ standard and everything to do with GCC's shameful diagnostics. A good compiler should tell you the parts of the standard that you need to know; that's what warnings/errors are for. The fact that GCC fails to provide decent diagnostics 75% of the time is another reason I'll be glad when that project finally dies.

rjmccall posted:

A well-designed default arguments implementation would not be seriously challenged by any of this.

Ok, you're the one making the assertion that this is easy. Write a proof of concept or a spec for it.

Avenging Dentist fucked around with this message at 17:53 on Aug 9, 2009

Avenging Dentist
Oct 1, 2005

oh my god is that a circular saw that does not go in my mouth aaaaagh
Looking online in more depth, one of the main reasons people cite for this is what happens not when looking at a static piece of code, but what happens when you change code (esp. base classes)?

alt.comp.lang.learn.c-c++ discussion
D discussion of why they felt they should/should not keep hiding

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe

Avenging Dentist posted:

A good compiler should tell you the parts of the standard that you need to know; that's what warnings/errors are for. The fact that GCC fails to provide decent diagnostics 75% of the time is another reason I'll be glad when that project finally dies.

I think everyone agrees that GCC's diagnostics are poor. Now, clang's diagnostics don't check whether there was a hidden match, either, but I'll make a note to fix that.

Avenging Dentist posted:

Looking online in more depth, one of the main reasons people cite for this is what happens not when looking at a static piece of code, but what happens when you change code (esp. base classes)?

The D thread brings up a lot of examples where changes to a base class can break a subclass without diagnostics. You will note that all of their real-world examples don't involve name hiding at all; it's only the constructed examples that do. Basically, inheritance inherently involves tight coupling, but most people don't think of it that way. If people were actually serious about this safety issue, they would require virtual methods to be redeclared in all subclasses; but of course people are not actually serious about it.

But yes, there are certainly dangers both ways.

Avenging Dentist posted:

Ok, you're the one making the assertion that this is easy. Write a proof of concept or a spec for it.

[basic.lookup.argdep]p3. Change "Let X be the lookup set produced by unqualified lookup" to "Let X be the lookup set produced by unqualified function lookup".

Insert a new section, [basic.lookup.unqual-function]:

[basic.lookup.unqual-function] Unqualified function lookup
1. This section is used only for the lookup of an unqualified name used as the postfix-expression of a function call.

2. Lookup proceeds as specified in [basic.lookup.unqual], except that if the result set includes a non-static member function declared on a class A, all non-static member functions with that name declared on base classes of A (whether direct or indirect) are added to the result set.

Avenging Dentist
Oct 1, 2005

oh my god is that a circular saw that does not go in my mouth aaaaagh

rjmccall posted:

I think everyone agrees that GCC's diagnostics are poor. Now, clang's diagnostics don't check whether there was a hidden match, either, but I'll make a note to fix that.

This is because clang's support for inheritance is still pretty limited. Until a few days ago, it didn't even allow for qualified-id lookup of members. Clang still has a long way to go before it's a real C++ compiler.

User0015
Nov 24, 2007

Please don't talk about your sexuality unless it serves the ~narrative~!
I'm trying to get a simple test C++ program working, but I'm loving something up badly. I've got two files that look like this:

in Foo.h
code:

class Bar;
class Foo
{
   public:   
      void Foowork(void) { bar.Barwork(); }
      void get_Bar(Bar* obj) { bar = obj; }
      
   private:
      Bar* bar;
};
in Bar.h
code:
class Bar
{
      void Barwork(void) { std::cout << "Bar works(??!?!?)"; }
      
};
What am I doing wrong here?

Vinterstum
Jul 30, 2003

User0015 posted:


What am I doing wrong here?

You didn't explain what the problem was (What's actually going wrong?)

But at a quick glance you're not treating "bar" as a pointer in Foowork() (use -> and not .). And naming a function "get_"-something when it's a setter is just weird.

Edit: Oh and if you actually want to call Barwork() in Foo.h, Bar needs a full definition and not just a forward declaration.

Vinterstum fucked around with this message at 22:43 on Aug 11, 2009

User0015
Nov 24, 2007

Please don't talk about your sexuality unless it serves the ~narrative~!
That's exactly what I want it to do.

If the problem is in the definition, how would it look? I tried a simple definition but it reported the same error(s) regardless. There's a bit more to it, or I'm omitting something obvious.

Vinterstum
Jul 30, 2003

User0015 posted:

That's exactly what I want it to do.

If the problem is in the definition, how would it look? I tried a simple definition but it reported the same error(s) regardless. There's a bit more to it, or I'm omitting something obvious.

The definition of class "Bar" is the lines of code you have in Bar.h. Without that, the compiler can't figure out what you're trying to do in Foo::Foowork. Just include Bar.h on the top of Foo.h (or better, move the definition of the Foowork() function to a .cpp file instead of a header file, so other files can include just Foo.h without getting Bar.h included in the bargain). I.e. Foo.h just declares the function as "void Foowork();", and Foo.cpp contains the actual definition as "void Foo::Foowork() { bar.Barwork();".

But for the future: It helps if you actually paste the error you get.

Avenging Dentist
Oct 1, 2005

oh my god is that a circular saw that does not go in my mouth aaaaagh
I think the problem is that you don't have a main function!

User0015
Nov 24, 2007

Please don't talk about your sexuality unless it serves the ~narrative~!

Avenging Dentist posted:

I think the problem is that you don't have a main function!

I don't need no stinkin' main.

I just omitted it. If you think it would help I guess I could paste it? It's...a line long? Two?

Avenging Dentist
Oct 1, 2005

oh my god is that a circular saw that does not go in my mouth aaaaagh

User0015 posted:

I don't need no stinkin' main.

I just omitted it. If you think it would help I guess I could paste it? It's...a line long? Two?

Well, if you want help for something like this, you should probably post the whole thing.

User0015
Nov 24, 2007

Please don't talk about your sexuality unless it serves the ~narrative~!
The original program would be to large to post. I just set up this simple program to show exactly what the issue was. I don't think the problem is related to anything in main. Does this code look like it should work, so you're asking for additional code because the problem is elsewhere? I threw this little example together so you (hopefully) wouldn't have to dig through extraneous code.

edit - I did forget to throw in the error message. It reads: "In member function 'void Foo::Foowork()' 'Barwork' has not been declared. request for member of non-aggregate type before '(' token. " Both errors on line 5 in Foo.cpp which is simply "bar.Barwork();"

If it's supposed to be bar->Barwork(); , the error reads as: "invalid use of undefined type 'struct Bar' " Line 6 in Foo.h reports error "forward declaration of 'struct Bar' " which is the line "Class Bar;"

User0015 fucked around with this message at 23:26 on Aug 11, 2009

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
You're posting excerpts that are obviously excerpts, and you're not telling us what errors you're getting, how you're running things, or really anything at all besides "something is wrong". Therefore you're forcing us to extrapolate your entire program structure and guess the problem.

The problem is probably that you don't understand header files, but we really can't be sure, because you've left out so much crap.

User0015
Nov 24, 2007

Please don't talk about your sexuality unless it serves the ~narrative~!
Alright then, I'll just post everything.

Foo.h
code:
#ifndef FOO_H
#define FOO_H



class Bar;
class Foo
{
   public:   
      void Foowork(void) { bar.Barwork(); }
      void get_Bar(Bar* obj) { bar = obj; }
      
   private:
      Bar* bar;
};


#endif
Foo.cpp is an empty file, since errors don't change if I move implementation to it or not. When it's NOT empty (which it currently IS) it looks like this:
[non-empty]Foo.cpp
code:
#include "Foo.h"

void Foo::Foowork()
{ 
    bar.Barwork();

};
Bar.h
code:
#ifndef BAR_H
#define BAR_H

class Bar
{
      
      void Barwork(void) { std::cout << "Bar works"; }
     
      };

#endif
main.cpp
code:
#include <cstdlib>
#include <iostream>
#include "Foo.h"
#include "Bar.h"

using namespace std;

int main(int argc, char *argv[])
{
    
    //I am empty
    
    system("PAUSE");
    return EXIT_SUCCESS;
}
Bar.cpp is also empty.

Vanadium
Jan 8, 2005

Looks like you are defining Foo::Foowork for a second time in foo.cpp for no real reason and also you are calling methods on Bar instances while only having a forward-declaration available, you need a proper definition of the class for anything but having a pointer to it that just sits there.

Vinterstum
Jul 30, 2003

User0015 posted:

edit - I did forget to throw in the error message. It reads: "In member function 'void Foo::Foowork()' 'Barwork' has not been declared. request for member of non-aggregate type before '(' token. " Both errors on line 5 in Foo.cpp which is simply "bar.Barwork();"

If it's supposed to be bar->Barwork(); , the error reads as: "invalid use of undefined type 'struct Bar' " Line 6 in Foo.h reports error "forward declaration of 'struct Bar' " which is the line "Class Bar;"

Try implementing both of the things I suggested at once (both include Bar.h in Foo.h (or move the definition to the cpp file and include Foo.h in that one), and change the . to ->), instead of just one of them at a time.

Vinterstum fucked around with this message at 23:40 on Aug 11, 2009

Adbot
ADBOT LOVES YOU

User0015
Nov 24, 2007

Please don't talk about your sexuality unless it serves the ~narrative~!

Vinterstum posted:

Try implementing both of the things I suggested at once (both include Bar.h in Foo.h (or move the definition to the cpp file and include Foo.h in that one), and change the . to ->), instead of just one of them at a time.

Well, I just tried this.

Foo.cpp
code:
#include "Foo.h"
#include "Bar.h"
void Foo::Foowork()
{ 
    bar->Barwork();

};
And

Bar.h - added "friend class Foo". It compiled. Doesn't work, but it compiled. That's always a good start.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply