LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   Question Regarding C++ Visibility (https://www.linuxquestions.org/questions/programming-9/question-regarding-c-visibility-750778/)

PatrickNew 08-28-2009 12:17 AM

Question Regarding C++ Visibility
 
Okay, so in an examination of nested classes in C++, I came upon this interesting problem. The following code compiles with no warnings with "-Wall -Wextra -ansi -pedantic" on gcc and "-Wall -Wcheck" on icc, leading me to believe that it is valid C++.

Code:

#include <iostream>

class Outer{
  private:
    struct Inner {
      int i;
      void print() { std::cout << i << std::endl; }
    };

  public:
    static Inner do_stuff() { Inner i = { 5 }; return i; }
};

int main()
{
  Outer::do_stuff().print();  //This works
  //Outer::Inner inner = do_stuff();  //This does not
  //inner.print();

  return 0;
}

If this code is run, it prints 5, exactly as expected.

My question is, why should this work? Outer::Inner is private, so it's print member function should not be visible to main. Indeed, one does get a compiler error if one switches out the commented lines with the one above them.

Any insight on why this is? Is there some situation I'm not foreseeing in which this behavior is desirable?

irkkaaja 08-28-2009 12:36 AM

Outer::dostuff() returns an Inner object. This object can call print, since it is allowed to access its own members. print() is actually public for this object; it is Inner which is private. main() can therefore call print() without any problems, though it is not allowed to declare a variable of type Inner, since it cannot "see" Inner. It works because C++ is weakly typed; instead of performing a typecheck on Outer::dostuff() at runtime, it just looks to see if it has a print() method, which it does. I'm pretty sure if you assigned Outer::dostuff() to a variable of type Object, or whatever C++'s basic object class is (sorry, I don't know much about C++), and then called print(), it wouldn't complain.

In other words, main() has no idea that it is calling Inner's print method, it is just calling something's print method.
http://en.wikipedia.org/wiki/Weak_typing

I'm pretty sure this would return a compiler error in a strongly-typed language like Ada or Java.

PatrickNew 08-28-2009 12:46 AM

Quote:

Originally Posted by irkkaaja (Post 3660384)
It works because C++ is weakly typed; instead of performing a typecheck on Outer::dostuff() at runtime, it just looks to see if it has a print() method, which it does.

I'm relatively sure that is not how this works. C++ is very statically typed, so the compiler knows the (compile-time) type of Outer::do_stuff() at compile time. Since print() is non-virtual, the call to print is bound at compile time, not runtime.

Quote:

I'm pretty sure if you assigned Outer::dostuff() to a variable of type Object, or whatever C++'s basic object class is (sorry, I don't know much about C++), and then called print(), it wouldn't complain.
C++ doesn't have such a class. Unlike Java, C++ has no requirement that there exist a singly-rooted inheritance tree, rather C++ has an inheritance not-necessarily-connected-DAG.

Quote:

In other words, main() has no idea that it is calling Inner's print method, it is just calling something's print method.
http://en.wikipedia.org/wiki/Weak_typing

I'm pretty sure this would return a compiler error in a strongly-typed language like Ada or Java.
In that sense, C++ is strongly typed. It's compiler does know that it is calling Inner:: print. For example, if I change the code to say Outer::do_stuff().Inner:: print(); I still don't get any errors.

I guess my real question is not so much why this is valid C++ in terms of "which are the relevant parts of the specification", but rather what I mean to ask is "why would anyone ever want it to be legal for a visible function to return an instance of an invisible class"?

ntubski 08-28-2009 10:08 AM

Quote:

Originally Posted by PatrickNew (Post 3660397)
I mean to ask is "why would anyone ever want it to be legal for a visible function to return an instance of an invisible class"?

Instead ask "why would anyone ever want it to be illegal for a visible function to return an instance of an invisible class?" If you are looking for a programming language that tries to prevent the programmer from writing badly designed programs, C++ is most definitely not it (try Ada for that). C++ is already overcomplicated and difficult to implement without adding restrictions to prohibit all the possible useless design patterns you could write in it.

PatrickNew 08-28-2009 11:29 AM

Quote:

Originally Posted by ntubski (Post 3660892)
Instead ask "why would anyone ever want it to be illegal for a visible function to return an instance of an invisible class?" If you are looking for a programming language that tries to prevent the programmer from writing badly designed programs, C++ is most definitely not it (try Ada for that). C++ is already overcomplicated and difficult to implement without adding restrictions to prohibit all the possible useless design patterns you could write in it.

Perhaps I should have provided the context of my question, because you make a very good point. I'm designing a language which has visibility for both classes and operations. It occurred to me one day that I was unsure how exactly this case (visible operations with invisible return types) would be handled, so I tried it in C++ to see what happens there (the object system of this language bears many similarities to the C++ object system). I figured if C++ forbid it, it was probably safe to forbid.

I just wanted to see what C++ did, and the decision surprised me. I am by no means saying that C++ should not do it that way, I'm just trying to understand what its use case is, because if some useful design requires it, then I don't want the language I'm designing to forbid useful designs.

However, if it is simply an oversight or an edge case that would be too complex to specify as invalid, then I won't worry about preserving the ability to do things like this.

ntubski 08-28-2009 04:10 PM

Quote:

I'm designing a language which has visibility for both classes and operations. It occurred to me one day that I was unsure how exactly this case (visible operations with invisible return types) would be handled, so I tried it in C++ to see what happens there (the object system of this language bears many similarities to the C++ object system). I figured if C++ forbid it, it was probably safe to forbid.
OK, I see where your question is coming from (although I'm not sure basing language design on C++ is a good idea). I can't think of any justification for Outer promising to return a private type from a public method. It seems like a hole in the visibilty system (and the friend keyword already allows making holes wherever the programmer needs them). Maybe if you were doing something really crazy with templates...

Code:

#include <iostream>

class Outer{
  private:
    struct Inner {
      int i;
      void print() { std::cout << i << std::endl; }
    };
  public:
    static Inner do_stuff() { Inner i = { 5 }; return i; }
};

template <typename T> void func(T t)
{
    t.print();
}

int main()
{
    func(Outer::do_stuff());
    return 0;
}

Another weird thing is that Outer::do_stuff().Inner::print(); works, whereas Outer::do_stuff().Outer::Inner::print(); doesn't, but they both mean the exact same thing.


All times are GMT -5. The time now is 08:01 AM.