1

I know it's impossible to do

class foo{
    foo bar;
};

Because the compiler wouldn't know how much memory to prepare for foo. But consider:

class A{
    B::C* foo;
};

class B{
    class C{
    };
};

It seems quite reasonable that the compiler would know exactly the size of A no matter the definition of the C. Why isn't this possible then? (Does the standard forbids that explicitly? for what?)


I got a very crude way by-pass the constraint that is to treat foo as a uintptr_t, then reinterpret_cast it back to C, this is just to show that I really don't get the (objective) reason, and I know it's a really really bad practice if used without decent reason.


Finally it turns out to be forbidden by the standard.

YiFei
  • 1,752
  • 1
  • 18
  • 33
  • 6
    This isn't specific to pointers or anything. You can't use names until they appear in general, barring members of the same class. – chris Jul 28 '16 at 01:51
  • @chris, isn't just saying it's a pointer enough for consideration on size? – YiFei Jul 28 '16 at 01:52
  • 2
    @YiFei `other` might not be a type name, it could be a variable's name, and so on. – songyuanyao Jul 28 '16 at 01:53
  • Then it would be tricky. How can other be a variable name when presented as in the place of a type name? – YiFei Jul 28 '16 at 01:56
  • 1
    Wait until you take a class on compiler design and implementation and study [LALR(1) parsers](https://en.wikipedia.org/wiki/LALR_parser). Then, come back here and ask this question again. – Sam Varshavchik Jul 28 '16 at 01:56
  • @YiFei So compiler error should be generated. Compiler need to check if you make such mistakes. – songyuanyao Jul 28 '16 at 01:59
  • @YiFei, C++ is full of context-dependent parsing. Consider [this example](http://coliru.stacked-crooked.com/a/b52895ed4cf86c45). In the first function, `B* A` is a multiplication. In the second function, the very same line is a variable declaration. – chris Jul 28 '16 at 02:03
  • @songyuanyao I got your point, as you expect compiler would have to check all its ambiguous meaning, but I was expecting that compiler should check for its correct meaning (once possible) then because one of its ambiguous absolutely don't work (as variable name), it then switch to treat it as a type name – YiFei Jul 28 '16 at 02:03
  • @chris, good example, but as in my comment to songyuanyao, if in the first struct of your example, you don't have a member field named B, I hope that the compiler would just pass on and assume it's a type name. – YiFei Jul 28 '16 at 02:12
  • @YiFei The compiler is not entitled to make such an assumption. – user207421 Jul 28 '16 at 02:19
  • @EJP, Yep, good to hear, but is it enforced by standard? – YiFei Jul 28 '16 at 02:20
  • In a template , all sorts of names are allowed without declaring them first, if they are scope-qualified with a template parameter. Ambiguity over whether such a name is a type is resolved with the `typename` keyword. It's not part of the language, but what OP seems to want could be achieved by allowing `typename` to do the same thing outside of a template. – Spencer Jul 28 '16 at 02:36
  • @Spencer Wow, cheers, you got it exactly, pity is that it's not part of language – YiFei Jul 28 '16 at 02:37
  • @YiFei Thus a comment. Besides, I'm not sure adding that capability would achieve anything. – Spencer Jul 28 '16 at 02:51
  • @Spencer Just for better OOP *experience*, since we don't then have to compromise to compilers (since I don't find standard forbids it) to declare nested class, see also [Jared Par's answer](http://stackoverflow.com/a/1021809/5215536) – YiFei Jul 28 '16 at 02:54

3 Answers3

1

One big challenge is following:

class other {};

namespace N {
  class foo {
    other* p;
  };

  class other {};
}

Which other* should be considered? Too much of work for compiler. Though we can be explicit by ::other or N::other, it still creates confusion in above case.

iammilind
  • 68,093
  • 33
  • 169
  • 336
  • Upvote! Agree that too much work for compiler (isn't it already much now?) :D; but see `Name lookup shall find an unambiguous declaration for the name` in the standard, which in my case is not involved. I would expect it will use the formerly declared should have priority, i.e. If declared, use declared, else pending for later check, if reach EOF, raise error then. – YiFei Jul 28 '16 at 03:07
  • @YiFei, thanks. Actually currently the above example code works fine & there is no ambiguity error. However, with your suggestion, that code may break. Moreover, whatever feature you are asking for, is already available using `template`s. – iammilind Jul 28 '16 at 05:04
  • 1
    Great hint! I'm to use explicitly instantiated templates in source file thanks – YiFei Jul 28 '16 at 05:12
0

TL;DR: That is because the standard dicates the syntax to be so. Unfortunately it's as simple as that.

This is the path I followed:

  1. You open standard e.g. c++11 draft.

  2. You seek section about classes it's section 9. You immediatietly see it has subsection "9.2 Class members". There is whole syntax defined. After some reading you get to member-declarator. The most important part in our context is declarator.

  3. Section 8. Declarators. In p4. you have the syntax for declarator. After going through all the formal definition of the fancy syntaxes supported you will notice that it hinges on id-expression.

  4. This is defined in 5.1.1 Primary Expressions. General ([expr.prim.general]). And it has to be a qualified-id of unqualified-id.

So your question could be rephrased to "Why does the namelookup not work like that?" But that is another question.

FWIW, not much I guess, but it's a design decision. It would need to keep track of all failed resolutions for the compilation unit. Doing lazy check in this context comes with quite far reaching implications.

luk32
  • 15,812
  • 38
  • 62
0

After the example

class foo{
    other* bar;
    ...
};
class other{
    ...
}

… which (1) uses an undeclared name, and (2) lacks a crucial semicolon, you ask

Why isn't this possible then? (Does the standard forbids that explicitly? for what?)

The Holy Standard requires that every name must be declared before it's used.

Note that in a given C++ implementation, even data pointers can be of different sizes (void* is guaranteed to be able to hold any data pointer), not to mention function pointers.

An alternative to declare-before-first-use requirement could be to adopt a default, like with implicit int in the early C days. E.g. the compiler could assume that other was a class type. But that would not be very helpful in general, and it would complicate the language.

However, in this particular case it's possible to combine the necessary minimum declaration of other with its first use:

class foo{
    class other* bar;
    //...
};

class other{
    //...
};

Alternatively you could use an ordinary forward declaration:

class other;

class foo{
    other* bar;
    //...
};

class other{
    //...
};

Just so the compiler knows that other is a class type, and not e.g. a function, or a synonym for char or void (pointers to these types are the largest, if there is any variability in the size of basic raw data pointers).


You then assert:

The dummy example I posted above may seem have no real meaning. The reason why I want to do without forward declaration is that when you got a nested class, there's no forward declaration that can preserve the encapsulation.

That's wrong.

E.g. this works nicely:

class Foo
{
private:
    class Other;
    Other* bar;

    class Other{};

public:
    Foo(): bar( new Other ) {}
};

However, the shortcut with declaration in the first use, class Other* bar;, doesn't work in this case. That's because it effectively would declare Other as a namespace level class instead of a nested one.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • Not to wrangle, but the standards only says "A declaration (Clause 7) may introduce one or more names into a translation unit or redeclare names introduced by previous declarations." It don't say we can't use name before declaration (I know I'm definitely wrong but don't know why or we don't need forward declarations any more), so where specifically in the standard states that. Thanks. – YiFei Jul 28 '16 at 04:09
  • @YiFei: You get that as a consequence of scope rules and name lookup. If a name hasn't been declared, name lookup fails. The standard requires that name lookup shall succeed. – Cheers and hth. - Alf Jul 28 '16 at 04:14
  • @YiFei I'm using the draft for 11 linked in my answer but I'm quite sure this didn't change. The definition for `id-qualifier` says: "*An identifier is an `id-expression` provided it has been suitably declared (Clause 7).*" and it probably isn't declared till it actually is. – luk32 Jul 28 '16 at 04:16
  • @luk32, thanks, I found that statement, I do think they should add a "before". – YiFei Jul 28 '16 at 04:20
  • @YiFei I think "*has been*" implies it. They would probably add "*or is declared at later point of the program*" in the other case. – luk32 Jul 28 '16 at 04:26
  • @luks32 Alright, I got it at 3.4.1-7-(7.1) (Unqualified name lookup), which explicitly stated that. What a pity. (I'm on 14 working draft) – YiFei Jul 28 '16 at 04:27
  • Consider [this question](http://stackoverflow.com/questions/951234/forward-declaration-of-nested-types-classes-in-c), from a OOP perspective, I don't really wanna break it to a un-nested class, thanks – YiFei Jul 28 '16 at 04:31