-2

It's been a while since I've done some C++ and I'm a little rusty. Could someone tell me how, if such a thing is possible, to declare a class object while forward declaring a class?

e.g.

class MyClass MyObject;

/* ... */

class MyClass
{
public:
    void MyVariable;
} MyObject;

I'm specifically asking so I can extern an object declaration like this:

// Header.hpp
class MyClass extern MyObject;


// Source.cpp
class MyClass
{
public:
    void MyVariable;
} MyObject;

As opposed to the low iq way, taking 2 lines:

// Header.hpp
class MyClass;
extern MyClass MyObject;

// Source.cpp
class MyClass
{
public:
    void MyVariable;
} MyObject;
Cryptoman
  • 63
  • 1
  • 6
  • Did you try it? – melpomene Jun 08 '18 at 08:10
  • Yeah, the error says "Type 'MyClass' is incomplete" – Cryptoman Jun 08 '18 at 08:11
  • Did you try the "low iq" way, too? – melpomene Jun 08 '18 at 08:11
  • Yeah, the "low iq" way works fine :P – Cryptoman Jun 08 '18 at 08:12
  • You can't. You can only declare a pointer to forward-declared class, but you want be able to use it meaningfully without the class definition (you'll be able only to pass it around). – Frax Jun 08 '18 at 08:12
  • i did flag this as a dupe of this one: https://stackoverflow.com/questions/4757565/what-are-forward-declarations-in-c?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa but then i realized that the other question is about forward declaring functions, however the answers also explain forward declarations of classes – 463035818_is_not_an_ai Jun 08 '18 at 08:34
  • @Frax: that was my first thought, but it turns out that you can declare **extern** objects of incomplete type. – joe_chip Jun 08 '18 at 08:52
  • Right, you can use extern. On the other hand, what would you use it for? The only thing you can do with it is take a pointer to it, or pass it by reference, so it's pretty much useless. – Frax Jun 08 '18 at 09:06
  • @Frax: not necessarily. I've updated my answer (I couldn't fit it in a comment). – joe_chip Jun 08 '18 at 19:14

2 Answers2

0

You can do it by putting extern in front of the type (because extern is a storage class and class MyClass is the type):

extern class MyClass myObject;

The above directly answers the original question about syntax. However, since there's apparently some confusion about this, I thought it's worth expending my answer.

First, relevant excerpts from the standard (n3690):

§ 3.1.2:

A declaration is a definition unless it declares a function without specifying the function’s body (8.4), it contains the extern specifier (7.1.1) or a linkage-specification 25 (7.5) and neither an initializer nor a function- body , it declares a static data member in a class definition (9.2, 9.4), it is a class name declaration (9.1), it is an opaque-enum-declaration (7.2), it is a template-parameter (14.1), it is a parameter-declaration (8.3.5) in a function declarator that is not the declarator of a function-definition , or it is a typedef declaration (7.1.3), an alias-declaration (7.1.3), a using-declaration (7.3.3), a static_assert-declaration (Clause 7), an attribute- declaration (Clause 7), an empty-declaration (Clause 7), or a using-directive (7.3.4).

§ 3.2.5:

[Note: The rules for declarations and expressions describe in which contexts complete class types are required. A class type T must be complete if:

— an object of type T is defined (3.1), or

— a non-static class data member of type T is declared (9.2), or

— T is used as the object type or array element type in a new-expression (5.3.4), or

— an lvalue-to-rvalue conversion is applied to a glvalue referring to an object of type T (4.1), or

— an expression is converted (either implicitly or explicitly) to type T (Clause 4, 5.2.3, 5.2.7, 5.2.9, 5.4), or

— an expression that is not a null pointer constant, and has type other than cv void* , is converted to the type pointer to T or reference to T using a standard conversion (Clause 4), a dynamic_cast (5.2.7) or a static_cast (5.2.9), or

— a class member access operator is applied to an expression of type T (5.2.5), or

— the typeid operator (5.2.8) or the sizeof operator (5.3.3) is applied to an operand of type T , or

— a function with a return type or argument type of type T is defined (3.1) or called (5.2.2), or

— a class with a base class of type T is defined (Clause 10), or

— an lvalue of type T is assigned to (5.17), or

— the type T is the subject of an alignof expression (5.3.6), or

— an exception-declaration has type T , reference to T , or pointer to T (15.3).

— end note]

Now, consider the first example from the question:

class MyClass MyObject;

/* ... */

class MyClass {} MyObject;

This is not valid, because the first line is both declaration and definition, per § 3.1.2).

The third example is valid, though:

// Header.hpp
class MyClass;
extern MyClass MyObject;

// Source.cpp
class MyClass {} MyObject;

This is valid, because the second line in Header.hpp is only a declaration (§ 3.1.2), and MyClass does not have to be a complete type at this point (the first bullet point in § 3.2.5).

The problem with the second example from the answer is simply incorrect syntax. As I wrote above, the correct way to do this is:

// Header.hpp
extern class MyClass myObject;

// Source.cpp
class MyClass {} myObject;

@Samer Tufail and @Frax suggested that this is useless. Maybe it's not very useful, but I can think about the following use cases:

  1. Forward declaring the type in order to reduce number of included header. The class can be defined in another header, included only when client code needs to access members of a global object.

  2. Being able to define a fixed number of global objects, while hiding class definition (in a source file). Such objects could be manipulated using free functions.

  3. Wrapping a global object (of class type) in C API:

Example of 3.:

// header.hpp
struct MyClass
{
    explicit MyClass(std::string s);

    const char* getString() const { return m_str.c_str(); }

private:
    std::string m_str;
};

extern "C"
{
    MyClass myObject("some string");

    const char* getString(const MyClass* obj) { return obj->getString(); }
}

// header.h (C API)
extern struct MyClass myObject;

const char* getString(const MyClass* obj);

There are other ways to do these things, but using forward declared type is possible. IMHO @Cryptoman asked about rarely used, but interesting feature, and there's no reason for downvoting the question.

joe_chip
  • 2,468
  • 1
  • 12
  • 23
  • To the added points: 1) Yep, but that is about forward class declaration, not about extern global variable declaration, 2) This is a reasonably valid use and actually one (and only one) I thought about, but really the better/simpler solution here is to use enum (as this would be effectively an enum). 3) I'm not sure what you mean here and what would you gain. I mean, it is still _declaring existence of an object of unknown type_. – Frax Jun 08 '18 at 19:43
  • 1) If you'd like to have extern declaration of the global and definition of the class in two different headers, then you'd have to use forward decl. for declaration of that global object. As for the rest of your comment - I'm not going to argue that these are good solutions - there are better ones. I just wanted to show that you can come up with a way to use it (since it is possible, it's nearly certain that some codebase out there is using this). I felt that your comments earlier suggested that using incomplete type in extern decl. is literally useless and I wanted to address that. – joe_chip Jun 08 '18 at 20:01
  • Anyway, OP asked about a syntax problem, not about pros and cons, so most of this post (as well as discussion) is slightly offtopic ;) I'm going to leave my answer, though - maybe someone will find it useful (and I've already typed to much to just delete it ;)) – joe_chip Jun 08 '18 at 20:08
  • Fantastic explanation. I really appreciate it @joe_chip. Interestingly, `class MyClass extern MyObject;` does, in fact, work. I'm not sure as to why, though. I suppose it's the same as `extern class MyClass MyObject;` or something? – Cryptoman Jun 08 '18 at 22:56
  • That's the sane thing. In C++ you can put declaration specifiers (type, const, volatile, extern, etc) in any order you want. The point is, `class MyClass` is a type specifier, so you can't put something in the middle. But otherwise, for example `extern const class MyClass` is the same thing as `const class MyClass extern` and `class MyClass const extern` and so on. – joe_chip Jun 09 '18 at 05:59
-1

You can only have a pointer or a reference to the forward declared class otherwise it is an incomplete type.

class MyClass;

class MyOtherClass
{
   public:
   // MyClass* obj;  pointer
   // MyClass& obj - reference
};

Why is this so?, imagine you now have MyClass obj in MyOtherClass. When the ctor for MyOtherClass is called it has to construct MyClass, which can't happen because it has no implementation and or an incomplete type.

Samer Tufail
  • 1,835
  • 15
  • 25
  • It's forbidden to have a *definition* of an object of an incomplete type, but extern declaration (as well as static class member declaration) is not a definition. Your answer does not apply in this question. What OP wants to do is possible, the only problem is syntax for this declaration. – joe_chip Jun 08 '18 at 11:08
  • Nothing can be accomplished with extern only, which is what the answer is trying to highligh. You can only do the only two things with the extern i.e reference or pointer, otherwise its useless, a comment above also highlights that fact. – Samer Tufail Jun 08 '18 at 11:10
  • I beg to differ. I've updated my answer to include examples. They may be a bit artificial, but definitely accomplish something. – joe_chip Jun 08 '18 at 19:12