这与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 UtilForDoingStuff
doStuff
重载已被定义似乎解决了问题。
但是标准的意思是“与函数参数的类型相关联的名称空间”是什么意思?不应该将using ::MyClassThatCanDoStuff
声明与名称空间内的scoped_foo
实例类型的显式范围一起触发与参数相关的查找,并且不应该找到doStuff()
的无主张定义?
此外,使用clang++ -ftemplate-delayed-parsing
编写的代码完全没有错误,它模拟MSVC的模板解析行为。至少在这种情况下,这似乎更可取,因为随时向名称空间添加新声明的能力是名称空间的主要吸引力之一。但是,如上所述,根据标准,似乎并不遵循法律的规定。它是允许的,还是它是一个不合格的实例?
编辑::正如KIIV指出的,有一个解决方法;代码将在编译时使用模板特化,而不是重载。我仍然想知道关于标准问题的答案。
....呵呵。是的,这似乎工作(虽然模板专业化必须在涉及多个文件的非平凡情况下声明为“内联”)。任何想法为什么超载版本不起作用,但专业化版本呢? –