18-07-2008, 08:06 PM
Hi Richard,
[ Note, the latest version of N1893 is currently N2103.]
Of course, example 2.3 is caught by adhering to rule 14–5–1.
However, examples 2.1 and 2.2 (in N2103) definitely need to be discussed, because neither 14–5–1 nor 14–6–2 give us a reason to call them "non-compliant", and in both examples, ADL plays a role in undesired names being found and selected (though it's not clear to me that ADL itself is necessarily at fault).
So let's look at Example 2.1: Things go wrong because the user wanted ::user::copy() to be selected in the call within ::user::g. But both ::std::copy and ::user::copy were considered because the type of x is composed of entities from different namespaces---namely, the template ::std::vector and the class ::user::Customer. As a result, both ::std and ::user became "associated namespaces", so they were both searched during ADL for "g".
Now let's look at Example 2.2: Again, things fall apart because we have two functions, ::std::tr1::tie and ::user::tie, that both compete during overload resolution when the user only expected ::user::tie to compete.
I'd like to introduce a new term:
In example 2.2, the fact that ::std::tr1::tie was found by ADL and ::user::tie was found by unqualified lookup seems like a distraction. The important point is:
So forget about ADL; we could concoct an equally dangerous (but MISRA C++-compliant!) scenario with a pair of using-declarations that should not be simultaneously visible. (And, as 2.1 and 2.2 demonstrate, the danger is not limited to call sites within a template instantiation.)
On the other hand, we can have completely safe and well-understood (possibly-templated) code that uses ADL (possibly at instantiation time) to find a function that is logically part of the interface of a given class (as is the case in both of the examples in 14–7–2, on the lines that are currently---and undeservedly---marked "non-compliant").
So I propose a two-part change: First, delete rule 14–7–2 (because it attacks legitimate and safe class interfaces without dealing with the root of the problem discussed in N2103). Second, introduce a new rule (which probably belongs in the "clause 13" section):
For the special case of built-in operators, I suspect they should be regarded as being in some sense "global", because they are usually in the initial overload set even if they do not become viable candidates. Therefore we should regard them as belonging to the global namespace for the purpose of this new rule (even though they don't really live there---or in any scope). So it follows that both of the ADL-using examples in rule 14–6–2, (b(t)) and (t==t), should be regarded as "compliant".
Now, I'm not totally sure whether the above formulation is quite right; for example, I could imagine a case where two candidates are not from "related" namespaces as defined above, but are instead from namespaces that are both related to a common namespace that is immediately enclosed by the global namespace (E.g., ::X::A::foo(int) and ::X::B::foo(char)), and which are intended to compete during overload resolution by the authors of ::X. But the new rule at least captures a lot of problematic cases and brings us much closer to what we really want.
richard_corden Wrote:The key motivation for this rule comes from a proposal to C++ 0X to "Fix ADL". (http://www.open-std.org/jtc1/sc22/wg21/d.../n1893.pdf)
Unfortunately C++ today does not have the recommended restrictions to ADL, and so the only way to ensure that none of the problems outlined here can occur is by disabling it completely.
One of the more interesting examples is [ Example 2.3]
[ Note, the latest version of N1893 is currently N2103.]
Of course, example 2.3 is caught by adhering to rule 14–5–1.
However, examples 2.1 and 2.2 (in N2103) definitely need to be discussed, because neither 14–5–1 nor 14–6–2 give us a reason to call them "non-compliant", and in both examples, ADL plays a role in undesired names being found and selected (though it's not clear to me that ADL itself is necessarily at fault).
So let's look at Example 2.1: Things go wrong because the user wanted ::user::copy() to be selected in the call within ::user::g. But both ::std::copy and ::user::copy were considered because the type of x is composed of entities from different namespaces---namely, the template ::std::vector and the class ::user::Customer. As a result, both ::std and ::user became "associated namespaces", so they were both searched during ADL for "g".
Now let's look at Example 2.2: Again, things fall apart because we have two functions, ::std::tr1::tie and ::user::tie, that both compete during overload resolution when the user only expected ::user::tie to compete.
I'd like to introduce a new term:
Quote:A namespace X is said to be related to a namespace Y if and only if X directly or indirectly encloses Y or vice versa.Example:
Code:
namespace N1 { namespace N2 {} }
namespace G {}
// N1 is related to N2 (and vice versa).
// N1 is not related to G.
// Both N1 and G are related to the global namespace.
In example 2.2, the fact that ::std::tr1::tie was found by ADL and ::user::tie was found by unqualified lookup seems like a distraction. The important point is:
- they're both viable for the call arguments and
- they come from *unrelated* namespaces.
So forget about ADL; we could concoct an equally dangerous (but MISRA C++-compliant!) scenario with a pair of using-declarations that should not be simultaneously visible. (And, as 2.1 and 2.2 demonstrate, the danger is not limited to call sites within a template instantiation.)
On the other hand, we can have completely safe and well-understood (possibly-templated) code that uses ADL (possibly at instantiation time) to find a function that is logically part of the interface of a given class (as is the case in both of the examples in 14–7–2, on the lines that are currently---and undeservedly---marked "non-compliant").
So I propose a two-part change: First, delete rule 14–7–2 (because it attacks legitimate and safe class interfaces without dealing with the root of the problem discussed in N2103). Second, introduce a new rule (which probably belongs in the "clause 13" section):
Quote:During overload resolution, the set of viable candidates shall not contain a pair of candidates that were declared in unrelated namespaces.
For the special case of built-in operators, I suspect they should be regarded as being in some sense "global", because they are usually in the initial overload set even if they do not become viable candidates. Therefore we should regard them as belonging to the global namespace for the purpose of this new rule (even though they don't really live there---or in any scope). So it follows that both of the ADL-using examples in rule 14–6–2, (b(t)) and (t==t), should be regarded as "compliant".
Now, I'm not totally sure whether the above formulation is quite right; for example, I could imagine a case where two candidates are not from "related" namespaces as defined above, but are instead from namespaces that are both related to a common namespace that is immediately enclosed by the global namespace (E.g., ::X::A::foo(int) and ::X::B::foo(char)), and which are intended to compete during overload resolution by the authors of ::X. But the new rule at least captures a lot of problematic cases and brings us much closer to what we really want.
<r>James Widman<br/>
-- <br/>
Gimpel Software<br/>
<URL url="http://gimpel.com">http://gimpel.com</URL></r>
-- <br/>
Gimpel Software<br/>
<URL url="http://gimpel.com">http://gimpel.com</URL></r>