Search code examples
c++templatesc++11using-declaration

Templates with same signature not causing a compiler error


The following program defines two function templates, A::foo<>() and B::foo<>(), in two separate namespaces (A and B). The two function templates are identical in signature, and differ only in the default argument assigned to their second template parameter. Eventually, their names are brought into the scope of main() by a corresponding pair of using declarations:

#include <type_traits>

namespace A
{
    template<
        typename T,
        typename = typename std::enable_if<
            std::is_same<T, int>::value        // Let this be condition C
            >::type
        >
    void foo(T) { }
}

namespace B
{
    template<
        typename T,
        typename = typename std::enable_if<
            !std::is_same<T, int>::value       // This is the negation of C
            >::type
        >
    void foo(T) { }
}

int main() {
    using A::foo;
    using B::foo; // COMPILES: Is this legal?

    foo(42); // Invokes A::foo(), non-ambiguous because of SFINAE
}

I would expect the second using declaration to cause a compilation error here: after all, that is what I get when I try to define those two templates in the same namespace.

To my surprise, every compiler I tried this on (GCC 4.7.2, GCC 4.8.0 beta, ICC 13.0.1, Clang 3.2) compiles the program and invokes A::foo().

QUESTION #1: Is this correct? Is it perhaps a case of "no diagnostic required"? References to the C++11 Standard are encouraged.


Consider now this variation of the above program, that basically achieves the same effect using classes rather than namespaces:

#include <type_traits>

struct X
{
    template<
        typename T,
        typename = typename std::enable_if<
            std::is_same<T, int>::value        // Here is condition C again
            >::type
        >
    static void foo(T) { }
};

struct Y
{
    template<
        typename T,
        typename = typename std::enable_if<
            !std::is_same<T, int>::value       // And the negation of C again
            >::type
        >
    static void foo(T) { }
};

struct Z : X, Y
{
   using X::foo;
   using Y::foo; // COMPILES: Is this legal?
};

int main() {
    Z::foo(42); // Invokes X::foo(), non-ambiguous because of SFINAE
}

This program too compiles on all the above mentioned compilers, while I would expect a compiler error to be caused by the second using declaration.

QUESTION #2: Is this correct? Is it perhaps a case of "no diagnostic required"? References to the C++11 Standard are encouraged.


Solution

  • For question #1: it appears that this is allowed, because there is no rule to disallow it. The C++11 standard mentions this in a note, next to the rule to disallow this, if the function introduced by the using declaration conflicts with a function that is declared directly in the namespace into which the using declaration introduces the name.

    It says in §7.3.3[namespace.udecl]/14:

    If a function declaration in namespace scope or block scope has the same name and the same parameter types as a function introduced by a using-declaration, and the declarations do not declare the same function, the program is ill-formed. [...]

    This is the normative text that designates a certain kind of conflict as invalid.

    There is no normative text that two using declarations don't conflict in the same way, but a note that a similar conflict between two using declaractions is not invalid at the point of declaraction. The same paragraph continues:

    [...] [ Note: Two using-declarations may introduce functions with the same name and the same parameter types. If, for a call to an unqualified function name, function overload resolution selects the functions introduced by such using-declarations, the function call is ill-formed. [ Example:

     namespace B { 
       void f(int); 
       void f(double); 
     }
     namespace C { 
       void f(int);
       void f(double); 
       void f(char);
     } 
    
     void h() {
       using B::f;    // B::f(int) and B::f(double)
       using C::f;    // C::f(int), C::f(double), and C::f(char)
       f(’h’);        // calls C::f(char)
       f(1);          // error: ambiguous: B::f(int) or C::f(int)?
       void f(int);   // f(int) conflicts with C::f(int) and B::f(int)
     }
    

    — end example ] — end note ]

    For question #2, the analogous normative text that describes the case of a conflict between a class member declaration and a class-level using declaration is in the following paragraph, §7.3.3/15:

    When a using-declaration brings names from a base class into a derived class scope, member functions and member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list (8.3.5), cv-qualification, and ref-qualifier (if any) in a base class (rather than conflicting). [ Note: For using-declarations that name a constructor, see 12.9. — end note ]

    Again there is no text about conflicts between using declaractions, but analogous to the note in the preceding case, having two using declarations that potentially designate conflicting functions is not ill-formed, because there is no text that disallows coexistence of these declaration. And as in the preceding case: if, for a function call, overload resolution selects both these functions the call is ill-formed.

    In your example SFINAE will always eliminate one of the potentially conflicting functions from the overload set, so in neither case is there a problem.