具有相同签名的模板不会导致编译器错误

时间:2013-03-01 00:06:42

标签: c++ templates c++11 using-declaration

以下程序在两个单独的命名空间(A::foo<>()B::foo<>())中定义了两个函数模板AB。这两个函数模板在签名相同,并且仅在分配给其第二个模板参数的默认参数上有所不同。最终,他们的名字被相应的一对main()声明带到了using的范围内:

#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
}

我希望第二个using声明在这里引起编译错误:毕竟,当我尝试在同一名称空间中定义这两个模板时,这就是我得到的结果。

令我惊讶的是,我尝试过的每个编译器(GCC 4.7.2,GCC 4.8.0 beta,ICC 13.0.1,Clang 3.2)编译程序并调用A::foo()

问题#1:这是正确的吗?是否可能是“无需诊断”的情况?鼓励参考C ++ 11标准。


现在考虑上述程序的这种变体,基本上使用类而不是名称空间实现相同的效果:

#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
}

这个程序也编译了所有上面提到的编译器,而我希望编译器错误是由第二个using声明引起的。

问题#2:这是正确的吗?是否可能是“无需诊断”的情况?鼓励参考C ++ 11标准。

1 个答案:

答案 0 :(得分:4)

对于问题#1:似乎允许这样做,因为没有规则禁止它。如果using声明引入的函数与直接在命名空间中引入名称的名称空间中声明的函数冲突,那么C ++ 11标准在注释中提到了这一点,在规则旁边禁止这个。

它在§7.3.3[namespace.udecl] / 14 中说:

  

如果命名空间作用域或块作用域中的函数声明具有   与a引入的函数相同的名称和相同的参数类型   using-declaration,声明声明不一样   功能,程序是不正确的。 [...]

这是将某种冲突指定为无效的规范性文本。

没有规范性文本,两个使用声明不会以相同方式发生冲突,而是注意两个使用声明之间的类似冲突在声明点处无效。同一段继续:

  

[...] [注意:两个使用声明可能会引入函数   相同的名称和相同的参数类型。如果,对于不合格的电话   函数名,函数重载决策选择函数   通过这种使用声明引入,函数调用是   病态的。   [例如:

 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)
 }
     

- 结束示例] - 结束注释]

对于问题#2,描述类成员声明与使用声明的类级别之间冲突的类似规范性文本在以下段落中,§7.3.3/ 15

  

当using声明将基类中的名称带入派生类时   类范围,成员函数和成员函数模板   派生类覆盖和/或隐藏成员函数和成员   具有相同名称的函数模板,参数类型列表(8.3.5),   基类中的cv-qualification和ref-qualifier(如果有的话)(相反)   而不是冲突)。 [注意:对于使用命名的声明   构造函数,见12.9。 - 结束说明]

同样没有关于使用声明之间的冲突的文本,但是类似于前面的情况中的注释,有两个使用可能指定冲突函数的声明并不是格式错误,因为没有文本不允许这些声明的共存。和前面的情况一样:如果对于函数调用,重载决策选择这两个函数,则调用格式不正确。

在您的示例中,SFINAE将始终从过载集中消除其中一个可能存在冲突的函数,因此在任何情况下都不存在问题。