Difference between forward declaration in argument vs "normal" forward declaration

198 views Asked by At

What is - if any - the difference between a forward declaration in a (template) argument (using an elaborated type specifier) and a "normal" forward declaration?

void foo(struct bar *);

// vs

struct bar;
void foo(bar *);

// ------ and also -------

std::unique_ptr<class Baz> one;

// vs

class Baz;
std::unique_ptr<Baz> two;
3

There are 3 answers

0
Michael Kenzel On BEST ANSWER

Let's begin by noting that "forward declaration" is a colloquialism used to refer to a certain common practical use of certain kinds of declarations. There is no such thing as a forward declaration as far as the C++ standard is concerned. There are just declarations.

With that in mind, I believe that there is no difference between

void foo(struct bar *);

and

struct bar;

as far as their effect on the name bar is concerned. Both declarations end up introducing the name of the struct bar if there is no previous declaration that already did so.

The relevant paragraph in C++17 would seem to be [basic.lookup.elab]/2 (emphasis mine):

If the elaborated-type-specifier is introduced by the class-key and this lookup does not find a previously declared type-name, or […] the elaborated-type-specifier is a declaration that introduces the class-name as described in [basic.scope.pdecl].

If an elaborated-type-specifier that doesn't contain a nested-name-specifier is encountered, unqualified name lookup is performed to see if the name already names a corresponding type. If no previously declared name is found, then the elaborated-type-specifier becomes a declaration of the class type of that name…

As pointed out by geza, the one way in which there can be a difference has to do with the scope into which the name is introduced. While

struct bar;

always introduces the name into the scope in which the declaration appears, an elaborated-type-specifier appearing as part of any other kind of declaration will introduce the name into the closest enclosing namespace [basic.scope.pdecl]/7.

0
cpplearner On

If you don't declare struct bar; beforehand, then struct bar might refer to type name declared in an outer scope (which might come from a completely different library). For example:

class bar {};
// ...
namespace ns {
    void foo(struct bar *);
}

^ Here the parameter of foo is pointer to the class bar in global scope.

class bar {};
// ...
namespace ns {
    struct bar;
    void foo(bar *);
}

^ Here the parameter of foo is pointer to ns::bar.

0
geza On

The difference can be the scope the declaration introduced. In case of elaborated type specifier, the declaration is introduced in the closest namespace, and this can make a difference.

For example:

struct Foo {
      void fn(struct Bar *);
};

Here, Bar is declared in the global namespace, not in Foo.