Forward declarations cause errors after code refactor
Asked Answered
A

2

5

My original class structure was similar to:

//def.h
namespace A
{
   struct X {};
}

and forward declarations where needed:

//file that needs forward declarations
namespace A { struct X; }

After some refactoring, X was moved to a different namespace, but to keep old code "working" using directives were used:

//def.h
namespace B
{
   struct X {};
}
namespace A
{
   using ::B::X;
}

Now we can access the same class keeping the old syntax A::X, but the forward declarations cause errors. The second problem is that the error message I'm getting doesn't point to where the forward declarations are, and finding/replacing the forward declarations is time-consuming.

For now I fixed the problem (the hard way).

What is the best approach to deal with this situation?

IMO, the using shouldn't be there at all, and all code that uses X should be refactored to accomodate the new namespace (this is one solution), but unfortunately this isn't an option.

The actual code is a lot more complicated, this is a simplified example.

Ariosto answered 9/1, 2013 at 13:0 Comment(9)
I'm guessing fwd.h is not def_fwd.h, that is, a header whose entire intent is to forward declare some other header names?Sack
@Sack it's just a name, the forward-declarations are per-implementation file.Ariosto
Well, that gives away my suggestion to deal with this. Taken from the Standard Library (see iosfwd), Boost (all over the place), et. al.Sack
Was X moved to an existing namespace B or was namespace A renamed to B? In the latter case, could you possibly use namespace aliases?Dosi
@rhalbersma existing namespace - A still exists.Ariosto
@LuchianGrigore What are your error messages?Dosi
@rhalbersma ideone.com/CxhctQAriosto
@LuchianGrigore you are mixing "direct" forward declarations and "indirect" ones through the using declarations. converting all forward declarations to the latter form in a single fwd header should help.Dosi
@LuchianGrigore I update my answer with a Standard quote about namespace member lookup rules.Dosi
D
4

I realize this is more about new code than about refactoring existing code, but I like using a special header called X_fwd.hpp for such cases.

// X_def.hpp
namespace B
{
   struct X {};
}
namespace A
{
   // NOT: using namespace B; // does not participate in ADL!      
   typedef ::B::X X;  // OR: using ::B::X;
}

// X_fwd.hpp
namespace A { struct X; }

// some file needing declaration of X
#include <X_fwd.hpp>

This makes it a lot easier to find forward declarations, and also to change them after the fact, because the change is isolated in one place only (DRY...).

NOTE1: AFAIK, there is no technical difference between using Peter Wood's answer of a typedef and your using declaration. Note that a using directive using namespace B; could cause trouble because these are ignored by Argument-Dependent-Lookup. Even worse, some of your code might then even silently call the wrong function overload because you are not pulling in the new namespace B anymore!

NOTE2: In the comments to the question, an Ideone example was given. This nicely illustrates a subtlety about name lookup inside namespaces: to quote from the draft Standard, section 3.4.3.2 Namespace members [namespace.qual], clause 2

For a namespace X and name m, the namespace-qualified lookup set S(X, m) is defined as follows: Let S'(X, m) be the set of all declarations of m in X and the inline namespace set of X (7.3.1). If S'(X, m) is not empty, S(X, m) is S'(X, m); otherwise, S(X, m) is the union of S(Ni, m) for all namespaces Ni nominated by using-directives in X and its inline namespace set.

This explains the following tricky ambiguity

namespace A
{
    struct X1{};
    struct X2{};
}

namespace B
{
    using A::X1;    // OK: lookup-set is both namespace A and B, and a single unique name is found (at this point!)
    struct X1;      // OK: lookup-set is namespace B, and a single unique name is found

    struct X2;      // OK: lookup-set is namespace B, and a single unique name is found
    using A::X2;    // error: lookup-set is both namespace A and B, and no unique name is found (at this point!)
}

So the validity of having both a direct declaration and a using-declaration with the same name inside a namespace is order-dependent. Hence the convenience of a single declaration in a fwd header file.

Dosi answered 9/1, 2013 at 13:28 Comment(4)
It's still an error to have a struct declaration and a typedef share a name. And typedefs don't affect ADL either.Cockneyfy
@SebastianRedl Tnx, updated to reflect subtle difference between using directive and typedef/using declaration.Dosi
+1 for the last subtlety - I saw this when fixing the issue myself but couldn't explain why.Ariosto
@LuchianGrigore I had quite a bit of fun finding the precise details. The Standard starts out quite hilariously in section 3.4: "The name lookup rules apply uniformly to all names [...]" and then followed by 14(!) pages of unqualified, qualified, using directives, using declarations and other intricate details ;-)Dosi
C
1

The best approach is to fix the code.

You can do it in two steps :

  1. fix all forward declares
  2. remove using ::B::X;
Ceja answered 9/1, 2013 at 13:6 Comment(1)
Yes, I thought so too, and this is what I did (without removing the using directives, that's not an option).Ariosto

© 2022 - 2024 — McMap. All rights reserved.