5

Consider this code:

class A
{
    struct B {};
    std::vector<B> _vec;

public:
    const std::vector<B>& get() const { return _vec; }
};

Note that B is private within class A. The above code compiles, but when calling get() from outside class A, it does not:

const std::vector<A::B>& vec = get(); // does not compile

Indeed, A::B is private. However, from C++11 on, you could simply do this:

const auto& vec = get();

which works perfectly.

For the same reason as above, you cannot do the following:

A::B obj; 

But, since there is a public getter, you can deduce the type from that getter function. In this particular case, one could do:

using B = std::decay<decltype(C{}.get())>::type::value_type;
B obj;

Questions

I'm not sure how to formulate my questions. It seems strange to me that, from C++11 on (and not before), we actually can instantiate A::B being the latter private. And even more, I think it's strange we can call const auto& get(). Is there any explanation for this? Wouldn't it be better not be allowed doing this? Should I declare A::B public? I feel like it doesn't make sense to declare it private if you need a getter function like the one in the above code.

mfnx
  • 2,894
  • 1
  • 12
  • 28
  • 2
    This is simply how the language works. *"Should I declare A::B public?"* Yes. – HolyBlackCat Sep 06 '19 at 13:42
  • 4
    very related/ dupe of first half of the Q: https://stackoverflow.com/questions/13532784/why-can-i-use-auto-on-a-private-type – NathanOliver Sep 06 '19 at 13:47
  • 2
    The *name* `A::B` is private. That's how the language works. It's ok if all that matters is that `A::B` fulfils some [Concept](https://en.cppreference.com/w/cpp/named_req) – Caleth Sep 06 '19 at 13:52
  • @NathanOliver thanks to point that out. A pity I couldn't find that post by myself. It seems access control is all about the names. – mfnx Sep 06 '19 at 14:14
  • If you were looking for a language that would not allow you to shoot yourself in the foot, C++ is not that language. – Mark Ransom Sep 06 '19 at 18:46
  • @MarkRansom Nor would any such language be usable to do real work. – curiousguy Sep 12 '19 at 14:48
  • @curiousguy it depends on what you mean by "real work". Java was intended to be a safer alternative to C++, and I understand it's very popular. – Mark Ransom Sep 12 '19 at 18:16
  • @MarkRansom Do you imply that you can't shoot yourself in the foot in Java? You can certainly expose your internal (private by design) objects, and you can do that easily. You can have very unpredictable thread behavior, and works in one compiler and breaks in another. Etc. – curiousguy Sep 12 '19 at 18:37
  • 1
    @curiousguy I meant in a general sense, not this particular example. *Every* language makes it possible to shoot yourself in the foot; C++ just makes it particularly easy. – Mark Ransom Sep 12 '19 at 21:11

2 Answers2

3

It seems strange to me that, from C++11 on (and not before), we actually can instantiate A::B being the latter private.

You could do it in C++98/03 just fine, through template argument deduction:

template<typename T>
void temp_func(const T &t)
{
...
T u = t;
}

temp_func(a.get()); //Will use the private type.

You can even use T inside of temp_func.

Making a type private was never a guarantee of making the type inaccessible from the outside. Private has always refer to the accessibility of the name, not of the construct behind the name. If you want a type to only be used within a scope, then that typename cannot be part of any non-private interfaces. And this has always been the case.

Should I declare A::B public?

That's up to you. However, if you expose a public user interface, you are making a statement that the user can, you know, use the interface. In order for the user to know how to use a vector<A::B>, they have to know how A::B behaves. So they have to use its definition. Even though it's private.

So if a user has to know about a type and about how a type behaves... is it really "private"?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
2

When you mark something private, it does not mean unaccessible. It simply mean that the declaration cannot be accessed from the outside.

For example, this will work:

class A {
    struct B {};

public:
    using C = B;
};

auto main() -> int {
    auto c = A::C{}; 
}

Here I access B, because I used the public alias to it.

Same thing for private members:

class A {
    int i;

public:
    auto the_i() -> int A::* {
        return &A::i;
    }
};

auto main() -> int {
    auto a = A{};
    auto member = a.the_i();

    // Whoa! I access the member directly! Shouldn't this prohibited?
    a.*member = 9;
}

The answer is no. The declaration is private, but can still be accessed by other means.

Same thing with member types, expose it in the public interface and users will be able to use it.

If you don't want users to use the private type, simply don't expose it in the public interface.

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • you access member i due to the indirection (you return a pointer). That worked before C++11 as well. The thing is that before c++11 we were not able to access members with private declared types. – mfnx Sep 06 '19 at 16:45
  • I don't understand the member pointer part here. Does someone expect that this doesn't work? – geza Sep 06 '19 at 16:50
  • @geza it was an analygy, that access is not prohibited, as long as you don't use the name. – Guillaume Racicot Sep 06 '19 at 17:03