2015-08-26 44 views
2

这与this question非常相似,但我不确定这个答案是否完全适用于我放在一起的证明该问题的最小代码。 (我的代码是而不是使用尾随返回类型,并且还有一些其他差异。)此外,MSVC的行为是否合法的问题似乎没有解决。命名空间导致次优模板重载分辨率

总之,当函数模板位于命名空间中时,我看到编译器选择了一个通用函数模板实例,而不是一个更具体的重载。

考虑下面的一组命名空间和类定义:

namespace DoStuffUtilNamespace 
{ 
    template<typename UNKNOWN> 
    void doStuff(UNKNOWN& foo) 
    { 
    static_assert(sizeof(UNKNOWN) == -1, "CANNOT USE DEFAULT INSTANTIATION!"); 
    } 
} 

class UtilForDoingStuff 
{ 
    public: 
    template <typename UNKNOWN> 
     void doStuffWithObjectRef(UNKNOWN& ref) 
     { 
     DoStuffUtilNamespace::doStuff(ref); 
     } 
}; 

class MyClassThatCanDoStuff { }; 

namespace DoStuffUtilNamespace 
{ 
    using ::MyClassThatCanDoStuff;  // No effect. 

    void doStuff(MyClassThatCanDoStuff& foo) { /* No assertion! */ } 
} 

...及以下的用例:

int main() 
{ 
    MyClassThatCanDoStuff foo; 
    DoStuffUtilNamespace::MyClassThatCanDoStuff scoped_foo; 
    UtilForDoingStuff util; 

    DoStuffUtilNamespace::doStuff(foo);   // Compiles 
    DoStuffUtilNamespace::doStuff(scoped_foo); // Compiles 
    util.doStuffWithObjectRef(foo);    // Triggers static assert 
    util.doStuffWithObjectRef(scoped_foo);  // Triggers static assert 
} 

如果整个DoStuffUtilNamespace消除其所有成员都感动到全球范围,这与G ++和Clang ++编译得很好。

命名空间doStuff当然是一个独立名称。按照top-voted answer on the similar question,标准说:

在解决相关的名称,从下列来源的名称被认为是:

  • 声明是在模板的定义点可见。

  • 来自命名空间的声明与来自实例化上下文和来自定义上下文的函数参数类型相关联。

这似乎有点奇怪我。我不明白为什么第一点会指定该声明必须在模板的定义点,而不是在实例点是可见的,因为第二个项目符号点明确规定,一些声明仅在实例化处可见的是允许的。 (如果有人想提供一个理由,我会很感激,但这不是我的问题,因为我的理解是,“为什么标准委员会决定X”的形式问题是脱离主题的。)

因此,我认为解释了为什么util.doStuffWithObjectRef(foo);触发静态断言:doStuff(MyClassThatCanDoStuff&)尚未在UtilForDoingStuff::doStuffWithObjectRef<UNKNOWN>(UNKNOWN&)的定义点处声明。确实在之后定义class UtilForDoingStuffdoStuff重载已被定义似乎解决了问题。

但是标准的意思是“与函数参数的类型相关联的名称空间”是什么意思?不应该将using ::MyClassThatCanDoStuff声明与名称空间内的scoped_foo实例类型的显式范围一起触发与参数相关的查找,并且不应该找到doStuff()的无主张定义?

此外,使用clang++ -ftemplate-delayed-parsing编写的代码完全没有错误,它模拟MSVC的模板解析行为。至少在这种情况下,这似乎更可取,因为随时向名称空间添加新声明的能力是名称空间的主要吸引力之一。但是,如上所述,根据标准,似乎并不遵循法律的规定。它是允许的,还是它是一个不合格的实例?

编辑::正如KIIV指出的,有一个解决方法;代码将在编译时使用模板特化,而不是重载。我仍然想知道关于标准问题的答案。

回答

2

对于名称空间,doStuff当然是一个从属名称。

你是从错误的前提开始的。没有像DoStuffUtilNamespace::doStuff(ref)这样的合格呼叫的ADL。 [basic.lookup。argdep]/P1,重点煤矿:

后缀表达式在一个函数调用(5.2.2)是 不合格-ID,其他名称空间不会在通常的 不合格考虑可能会搜索查找(3.4.1),并且在这些名称空间中,可能会找到 名称空间范围的朋友函数或函数模板声明 (11.3)。

DoStuffUtilNamespace::doStuff合格-ID,而不是一个不合格-ID。 ADL不适用。因此,DoStuffUtilNamespace::doStuff也不是依赖名称。 [temp.dep]/P1:

在形式的表达:

后缀表达式(表达式列表选择

其中后缀 - 表达式不合格id-, 不合格id-表示从属名称如果[...]。如果运算符的操作数是与类型相关的表达式,则运算符也表示 依赖名称。这些名称绑定,并在 点模板实例(14.6.4.1)中的 模板定义两者的背景和实例化点的上下文中查找

(的依赖的斜体命名表明,本款定义的术语)

相反,每[temp.nondep]/P1:

在模板定义中使用

非依赖名字使用发现通常的名称查找和绑定在他们使用的位置。

找不到您以后的重载声明。


专业化的作品,因为它仍然是使用相同的功能模板声明;你只是提供了一个不同于默认实现的实现。


但究竟是什么标准的意思是“与 类型的函数参数相关联的命名空间”?应该不是using ::MyClassThatCanDoStuff声明,与scoped_foo实例类型的 命名空间,触发参数依赖查找

号using声明■不要影响ADL中明确的范围界定一起 。 [basic.lookup.argdep]/P2,重点煤矿:

对于在函数调用每个参数类型T,有一组 零个或多个相关联的命名空间和一组零个或多个相关联的 类要考虑。命名空间和 类的集合完全由函数参数 (以及任何模板模板参数的命名空间)的类型决定。 typedef名称以及使用声明用于指定类型就做无助于这一套。命名空间和类的组被 以如下方式确定:

  • 如果T是一个基本的类型,[...]

  • 如果T是一个类类型(包括工会),其相关的类是:类本身;它是其成员的类别(如果有的话);和它的 直接和间接基类。其关联的名称空间是其相关类的最内层封闭名称空间。此外,如果T是类模板专业化,则其关联的名称空间和类别还包括:与为模板类型参数 (不包括模板模板参数)提供的模板参数类型相关联的名称空间和类。任何 模板模板参数都是成员的名称空间;以及用作模板模板参数的任何 成员的类都是成员。 [ 注意:非类型的模板参数不贡献于关联的命名空间的集合。 - 注完]

  • [...]

1

随着模板特殊化,我可以得到它的工作:从同类型的无论是从实例化上下文和自定义背景功能参数相关的命名空间

namespace DoStuffUtilNamespace 
{ 
    template<typename UNKNOWN> 
    void doStuff(UNKNOWN& foo) 
    { 
    static_assert(sizeof(UNKNOWN) == -1, "CANNOT USE DEFAULT INSTANTIATION!"); 
    } 
} 

class UtilForDoingStuff 
{ 
    public: 
    template <typename UNKNOWN> 
     void doStuffWithObjectRef(UNKNOWN& ref) 
     { 
     DoStuffUtilNamespace::doStuff(ref); 
     } 
}; 

class MyClassThatCanDoStuff { }; 


namespace DoStuffUtilNamespace 
{ 
    using ::MyClassThatCanDoStuff; 
    template <> void doStuff<MyClassThatCanDoStuff>(MyClassThatCanDoStuff& foo) { /* No assertion! */ } 
} 


int main() 
{ 
    MyClassThatCanDoStuff foo; 
    DoStuffUtilNamespace::MyClassThatCanDoStuff scoped_foo; 
    UtilForDoingStuff util; 

    DoStuffUtilNamespace::doStuff(foo);   // Compiles 
    DoStuffUtilNamespace::doStuff(scoped_foo); // Compiles 
    util.doStuffWithObjectRef(foo);    // Compiles 
    util.doStuffWithObjectRef(scoped_foo);  // Compiles 
} 
+0

....呵呵。是的,这似乎工作(虽然模板专业化必须在涉及多个文件的非平凡情况下声明为“内联”)。任何想法为什么超载版本不起作用,但专业化版本呢? –

0

声明。

用下面的代码

例主叫?::foo(const B::S&)当它打印B::fooDemo

namespace A 
{ 
    template <typename T> 
    void foo(const T&) {std::cout << "A::foo" << std::endl;} 

    template <typename T> 
    void bar(const T& t) { 
     foo(t); // thank to ADL, it will also look at B::foo for B::S. 
    } 
} 

namespace B 
{ 
    struct S {}; 

    void foo(const S&) {std::cout << "B::foo" << std::endl;} 
} 

int main() 
{ 
    B::S s; 
    A::bar(s); 
} 

所以,第二个项目符号点增加B::foo过载能力的列表。

为什么模板专业化在这种情况下

工作只有一个功能:

template<> 
void DoStuffUtilNamespace::doStuff<MyClassThatCanDoStuff>(MyClassThatCanDoStuff& foo); 
哪怕是后面定义

。 请注意,在翻译单元中应该知道存在专业化的事实,否则程序会生病(不尊重ODR)。

虽然重载没有。

你想:

所以,我认为这可以解释为什么util.doStuffWithObjectRef(foo);触发静态断言:doStuff(MyClassThatCanDoStuff&)还没有在UtilForDoingStuff::doStuffWithObjectRef<UNKNOWN>(UNKNOWN&)定义的点被宣布。确定在doStuff过载之后确定移动类UtilForDoingStuff的定义似乎解决了这个问题。

没错。