在命名空间中重载函数模板

9
为什么这段代码在GCC 4.4下编译失败?
template<typename T>
class A {
public:
    void foo () {

    }

private:
    T x;
};

namespace Ns {
template<typename T>
void do_it (A<T> a) {
    a.foo ();
}
};

template<typename T>
void myfun (T x) {
    Ns::do_it (x);
}

template<typename T>
class B {
public:
    void bar () {

    }

private:
    T x;
};

namespace Ns {
template<typename T>
void do_it (B<T> b) {
    b.bar ();
}
};

int main () {
    A<int> a;
    B<int> b;

    myfun (a);
    myfun (b); // error: no matching function call to do_it(B<int>&)

    return 0;
}

这可能与do_it的命名空间有关。当我移除它周围的命名空间时,代码可以编译。

背景:我正在构建一组可用于许多不同容器类的函数集。为了统一处理不同的接口,我使用独立的函数,并针对每个容器类进行重载。这些函数应该放在命名空间中,以避免将它们杂乱地放在全局命名空间中。

B的定义应该来自与A不同的头文件,因此重新排序不是一个选项。


这是一个打字错误!<T>被HTML吞掉了。 - Roland W
VS 2010编译了上述代码,我相信这样做是正确的,但这是一个棘手的例子。好问题! - David Rodríguez - dribeas
和VS2008一样,我刚刚检查了它。这可能是GCC的一个bug,还是他们的解释与微软不同?它在没有命名空间的情况下工作的事实将指向它是一个bug,不是吗? - Roland W
1个回答

7
原因在于只有在调用点完成了ADL。其他函数查找仅在myfun函数模板的定义中完成。
而在该定义上下文中,仅声明接受A<int>do_it重载。
编辑:如果您想要一个标准参考,请参阅[temp.dep.candidate]和[temp.res]p1。

2
@Mark 如果你想让命名空间Ns中的函数被考虑,你可以从在该命名空间中定义的一个虚类派生出B<T>,这样ADL将会查找Ns命名空间。或者你可以传递模板参数给B<T>,将生成的特化与Ns相关联。例如B<NS::ADLAssociator<int>>(ADLAssociator可能只是一个小的类模板,仅存储T t;)。或者你可以将这些关联器类作为额外的虚参传递:B<int, NS::ADL>。有很多选项可供选择。最干净的方法似乎是从一个类中派生B,或者在NS中定义它。 - Johannes Schaub - litb
5
将自由函数改成静态类成员函数怎么样:namespace Ns { template <typename T> struct dispatch; }(先声明),然后为每个新类型添加一个特化: namespace Ns { template <typename T> struct dispatch< B<T> > { static void do_it( B<T> x ) { x.bar() } }; },最后用template <typename T> void myfunc( T t ) { Ns::dispatch<T>::do_it( t ); }来调用。 - David Rodríguez - dribeas
1
@David 是的,那也可以,好主意。不会依赖脆弱的 ADL。 - Johannes Schaub - litb
@JohannesSchaub-litb:另外,你的前两个建议似乎不起作用。(请参见thisthis)不过,我没有尝试第三个建议。 - Nawaz
@JohannesSchaub-litb:哎呀,我错过了那个。谢谢。 - Nawaz
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接