Scope of the c++ using directive
Asked Answered
G

2

5

From section 7.3.4.2 of the c++11 standard:

A using-directive specifies that the names in the nominated namespace can be used in the scope in which the using-directive appears after the using-directive. During unqualified name lookup (3.4.1), the names appear as if they were declared in the nearest enclosing namespace which contains both the using-directive and the nominated namespace. [ Note: In this context, “contains” means “contains directly or indirectly”. —end note ]

What do the second and third sentences mean exactly? Please give example.

Here is the code I am attempting to understand:

namespace A
{
    int i = 7;
}
namespace B
{
    using namespace A;
    int i = i + 11;
}
int main(int argc, char * argv[])
{
    std::cout << A::i << " " << B::i << std::endl;
    return 0;
}

It print "7 7" and not "7 18" as I would expect.

Sorry for the typo, the program actually prints "7 11".

Gobang answered 20/9, 2012 at 16:1 Comment(11)
The behaviour here is undefined. Both i instances inside of namespace B refer to the same variable (it shadows A::i as soon as its declarator is seen, that is, before the initializer). So it is initialized with its own garbage value incremented by 11.Influent
OK, why does it shadow A::i after using namespace A. Is this what the paragraph from the standard is talking about?Gobang
@ThomasMcLeod: That's what shadowing means: a definition is hidden. Since you didn't qualify your use of i in the expression, it takes it from lookup. And since C++ allows you to refer to variables you just defined even in the initialization expression, the i will be the new one, not the one from A.Hartman
Something strikes me very strange here, which is to say, it's allowing addition of non-const data at compile time?Opportina
@std''OrgnlDave: while I am sure that an optimizing compiler can do the addition at compile time, given that even without const qualifiers it knows the values at compile time, I don't see why you think it must be taking place at compile time. In any event, the example provokes undefined behavior, which means the entire program is undefined ( blog.llvm.org/2011/05/… and posts linked to from there) and could do anything at all, including outputting nothing at all, outputting the text of "Romeo and Juliet," etc.Shellfish
In this case, the effect of using namespace A is to make names defined in A accessible as if they were defined in the global namespace. You can try to define ::i and remove using namespace A, the effect will be exactly the same.Influent
@n.m., thanks. Do you still think the behavior is undefined?Gobang
It is still undefined for exactly the same reason as yesterday.Influent
@n.m., but static storage during variables are initialized to zero. Hence, right after the declarator int i in namespace B shouldn't i be zero-initialized?Gobang
Yes, you are right, there's zero-initialization taking place, I forgot about it. The result is not in fact undefined and should be 11.Influent
Running through Clang analyzer produces the warning message "variable 'i' is uninitialized when used within its own initialization [-Wuninitialized] int i = i + 11;"Shellfish
O
4

Eliminating the undefined behaviour:

namespace A
{
    int i = 7;
}
namespace B
{
    using namespace A;
    int tmp = i + 11;
    int i = tmp;
}
#include <iostream>
int main()
{
    std::cout << A::i << " " << B::i << std::endl;
    return 0;
}

The meaning of the standard is that at the line

    int tmp = i + 11;

the name i appears in the "nearest enclosing namespace which contains both the using-directive and the nominated namespace"; the using-directive appears in namespace B while the nominated namespace is namespace A; the nearest enclosing namespace is the global namespace, so i appears as ::i. This means that if a name i is already present in the global namespace the code is ambiguous.

For a more complex example:

namespace A {
    namespace B {
        namespace C {
            int i = 4;
        }
    }
    namespace D {
        using namespace B::C;
        namespace E {
            int j = i;
        }
    }
}

At the line int j = i, i appears in the nearest enclosing namespace of the using-directive (i.e., A::D) and the nominated namespace (A::B::C), which is A. So, within A::D after the using-directive, and so also within A::D::E, the unqualified name i can refer to A::B::C::i appearing as A::i, shadowing any ::i, conflicting with any A::i, and being shadowed by any A::D::i or A::D::E::i (within A::D::E):

int i = 1;                // shadowed by A::B::C::i appearing as A::i
namespace A {
    int i = 2;            // conflicts with A::B::C::i appearing as A::i
    namespace B {
        int i = 3;        // irrelevant
        namespace C {
            int i = 4;    // nominated; appears as A::i
        }
    }
    namespace D {
        int i = 5;        // shadows A::B::C::i appearing as A::i
        using namespace B::C;
        namespace E {
            int i = 6;    // shadows A::B::C::i appearing as A::i
            int j = i;
        }
    }
}

Note that just because the name appears as A::i during unqualified name lookup, that does not mean that it actually is there; the qualified name A::i will continue to refer only to an actual name A::i (if any exists).

Oira answered 21/9, 2012 at 13:37 Comment(4)
I'm still unsure of how this should work. So when the standard says "appears", is that only for the purpose of name hiding aka shadowing? If that's the case, then the above code should result in a name collision at int j = i.Gobang
@Gobang which code are you referring to? Which i is colliding?Oira
What I meant to say was that, if we commented out the statements int i = 5; and int i = 6;, then there should be a name collision involving i at the statement int j = i;. But in the above code the presence of A::D::i and A::D::E::i masks the collision.Gobang
@Gobang yes, that's right. As you note, the collision only matters for the statement int j = i, where the unqualified name i is ambiguous (if A::D::i and A::D::E::i are not present).Oira
N
5

The using statement in your code is irrelevant. B::i is already in scope when the initializer for B::i is evaluated. You can trivially prove this by deleting the using statement; your code should compile and run just the same. In any case, the value of B::i ends up being undefined because it depends on an uninitialized value (i.e. the value that B::i had when the initializer was evaluated).

Nerveracking answered 20/9, 2012 at 22:17 Comment(0)
O
4

Eliminating the undefined behaviour:

namespace A
{
    int i = 7;
}
namespace B
{
    using namespace A;
    int tmp = i + 11;
    int i = tmp;
}
#include <iostream>
int main()
{
    std::cout << A::i << " " << B::i << std::endl;
    return 0;
}

The meaning of the standard is that at the line

    int tmp = i + 11;

the name i appears in the "nearest enclosing namespace which contains both the using-directive and the nominated namespace"; the using-directive appears in namespace B while the nominated namespace is namespace A; the nearest enclosing namespace is the global namespace, so i appears as ::i. This means that if a name i is already present in the global namespace the code is ambiguous.

For a more complex example:

namespace A {
    namespace B {
        namespace C {
            int i = 4;
        }
    }
    namespace D {
        using namespace B::C;
        namespace E {
            int j = i;
        }
    }
}

At the line int j = i, i appears in the nearest enclosing namespace of the using-directive (i.e., A::D) and the nominated namespace (A::B::C), which is A. So, within A::D after the using-directive, and so also within A::D::E, the unqualified name i can refer to A::B::C::i appearing as A::i, shadowing any ::i, conflicting with any A::i, and being shadowed by any A::D::i or A::D::E::i (within A::D::E):

int i = 1;                // shadowed by A::B::C::i appearing as A::i
namespace A {
    int i = 2;            // conflicts with A::B::C::i appearing as A::i
    namespace B {
        int i = 3;        // irrelevant
        namespace C {
            int i = 4;    // nominated; appears as A::i
        }
    }
    namespace D {
        int i = 5;        // shadows A::B::C::i appearing as A::i
        using namespace B::C;
        namespace E {
            int i = 6;    // shadows A::B::C::i appearing as A::i
            int j = i;
        }
    }
}

Note that just because the name appears as A::i during unqualified name lookup, that does not mean that it actually is there; the qualified name A::i will continue to refer only to an actual name A::i (if any exists).

Oira answered 21/9, 2012 at 13:37 Comment(4)
I'm still unsure of how this should work. So when the standard says "appears", is that only for the purpose of name hiding aka shadowing? If that's the case, then the above code should result in a name collision at int j = i.Gobang
@Gobang which code are you referring to? Which i is colliding?Oira
What I meant to say was that, if we commented out the statements int i = 5; and int i = 6;, then there should be a name collision involving i at the statement int j = i;. But in the above code the presence of A::D::i and A::D::E::i masks the collision.Gobang
@Gobang yes, that's right. As you note, the collision only matters for the statement int j = i, where the unqualified name i is ambiguous (if A::D::i and A::D::E::i are not present).Oira

© 2022 - 2024 — McMap. All rights reserved.