Problems with Rule 14–6–2

Moderators: david ward, misra cpp

Post Reply
James Widman
Posts: 9
Joined: Fri Jun 20, 2008 9:17 pm
Company: Gimpel Software

Problems with Rule 14–6–2

Post by James Widman » Wed Jul 09, 2008 9:30 pm

Hi all,

The second example in 14–6–2 is:

Code: Select all

template <typename T> 
void f ( T const & t ) 
{ 
   t == t;                       // Non-compliant - Calls NS::operator== 
                                 // declared after f 
   ::operator ==( t, t );        // Compliant - Calls built-in operator== 
   ( operator == <T> ) ( t, t ); // Compliant - Calls built-in operator== 
} 

namespace NS 
{ 
   struct A 
   { 
      operator int32_t ( ) const; 
   }; 
   bool operator== ( A const &, A const & ); 
} 

int main ( ) 
{ 
   NS::A a; 
   f ( a ); 
}
One problem is that the expression '::operator==(t,t)' renders the program ill-formed at template definition time (which means that, if your compiler conforms to ISO C++ 2003, it will issue an error at template definition time and refuse to generate code). This is given by ISO C++ in 14.6 temp.res, which contains this bit of normative text:
If a name does not depend on a template-parameter (as defined in 14.6.2), a declaration (or set of declarations) for that name shall be in scope at the point where the name appears in the template definition; the name is bound to the declaration (or declarations) found at that point and this binding is not affected by declarations that are visible at the point of instantiation.
Because of the way 'operator==' is written, we know that it is not a dependent name. So unqualified name lookup searches for 'operator==' and finds nothing because there is no declaration of that operator before f (either in this example or in the previous example). So the program is ill-formed (diagnostic required).

Likewise, 'operator==<T>' would also render the program ill-formed (if it were not ill-formed already) because there is no operator== template in scope, so the '<' used there is the less-than operator and not the beginning of a template argument list.

Also, the comment following each expression is wrong: the names of built-in operators do not exist in the global namespace---or anywhere else---so they cannot be found by name lookup. So the only correct way to write f() in a way that uses operator== over T and allows [T=int] at instantiation time is to write the non-compliant version (t == t).

So we really need to question the wisdom of this rule.

I realize that MISRA is trying to protect people from effects of unwanted ADL (and that's a good thing), but doesn't rule 14–5–1 catch the really scary cases? A non-generic function like NS::operator==(A const &,A const &) is *supposed* to be found by ADL. Ditto for NS::b(A const&) in the first example.

Can anyone cite a real-world case where finding a non-generic, non-template function (including operator functions) by ADL leads to a real problem?

If not, I suggest that this rule be downgraded from "Required" to "Advisory" or else removed altogether.
James Widman
--
Gimpel Software
http://gimpel.com

richard_corden
Posts: 4
Joined: Thu Sep 22, 2005 9:33 am
Location: Hersham

Re: Problems with Rule 14–6–2

Post by richard_corden » Wed Jul 16, 2008 4:44 pm

Firstly, the example is incorrect. Thank you for highlighting this.

The key motivation for this rule comes from a proposal to C++ 0X to "Fix ADL". (http://www.open-std.org/jtc1/sc22/wg21/ ... /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 the following:

// Example 2.3
//
#include <vector>
namespace N {
struct X { };
template<typename T>
int* operator+( T , unsigned )
{ static int i ; return &i ; /* just to stub in the function body */ }
}

However, please read the complete paper for a description on why ADL in its current form is of concern.


Regards,

Richard
--
Richard Corden
Programming Research Ltd.
[email protected]
+ 44 845 0048478

James Widman
Posts: 9
Joined: Fri Jun 20, 2008 9:17 pm
Company: Gimpel Software

Re: Problems with Rule 14–6–2

Post by James Widman » Fri Jul 18, 2008 9:06 pm

Hi Richard,
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/ ... /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]
<nod>
[ 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:
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: Select all

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.
That seems to be the true recipe for disaster.

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):
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.
James Widman
--
Gimpel Software
http://gimpel.com

James Widman
Posts: 9
Joined: Fri Jun 20, 2008 9:17 pm
Company: Gimpel Software

Re: Problems with Rule 14–6–2

Post by James Widman » Fri Jul 18, 2008 10:36 pm

James Widman wrote: During overload resolution, the set of viable candidates shall not contain a pair of candidates that were declared in unrelated namespaces.
FYI, this is too strict for ordinary C++ programming---e.g., if a class X has an implicit conversion to a primitive type or to std::string and you also give X an overloaded operator<< for output streams, then ADL will find both the class's op<< in the namespace enclosing X as well as other viable overloads of op<< in namespace std. And customization techniques that deliberately take advantage of ADL would also violate this rule.

However, it might still be an acceptable rule to follow when writing safety-critical code.
James Widman
--
Gimpel Software
http://gimpel.com

misra cpp
Posts: 151
Joined: Mon Jun 02, 2008 1:55 pm
Company: MISRA

Re: Problems with Rule 14–6–2

Post by misra cpp » Tue Oct 11, 2016 11:10 am

A Technical Corrigendum will correct this as:

The following forward template declaration for operator== will be added to the top of the example:

template <typename T>
bool operator==(T const &, T const &); // #1

Comments need changing to say // Compliant - calls ::operator== #1, above
Posted by and on behalf of
the MISRA C++ Working Group

Post Reply

Return to “6.14 Templates (C++)”