在 MSVC 上编译的模板成员函数专用化的情况,而不是在其他函数上编译的情况



[ 编辑 ] 我将标题从 works 更改为 compiles,因为事实证明它毕竟不起作用(感谢@bogdan的评论)。我在帖子末尾添加了代码,显示了原因和方式。

问题的第二部分仍然存在——有没有办法"修复"它?问题的关键是将一个虚函数Observe在基template<int N> class X重新路由到从X<N>派生的类中Observe<N>的模板化函数,而不需要任何X支持代码。

有关如何通过要求X合作来完成此操作的示例,请参阅另一个问题的回答(基本上要求将Observe<N>声明到派生最多的类中)。


在查看另一个问题选择要覆盖的方法时,我发现以下代码片段在 vc++ 2015 上干净地编译(使用 /W4 /Za )并返回预期的输出,但无法在 gcc-5.1clang 3.7 上编译(在 ideone.com 尝试)。

我知道模板函数的专业化有很多陷阱,但我仍然很好奇在这种情况下适用C++标准的哪个字母,以及 - 在代码不完全兼容的情况下 - 是否有一种简单的方法来"修复"它。

#include <iostream>
using std::cout;
using std::endl;
typedef int Parameter;
class Observer
{
public:
    virtual void Observe(Parameter p) = 0;
};
class TaggedDispatch
{
public:
    template<size_t Tag> void TObserve(Parameter p);
};
template<size_t Tag>
class TaggedObserver : virtual public TaggedDispatch, public Observer 
{ 
public:
    virtual void Observe(Parameter p) override
    {   TObserve<Tag>(p); }
};
class Thing : public TaggedObserver<0>, TaggedObserver<11>
{   };
template<> void Thing::TObserve<0>(Parameter p)
{   cout << "Parent #  0, Parameter " << p << endl; }
template<> void Thing::TObserve<11>(Parameter p)
{   cout << "Parent # 11, Parameter " << p << endl; }
int main(int, char **)
{
    Thing test;
    test.TObserve<0>(101);
    test.TObserve<11>(999);
    return 0;
}

使用 vc++ 2015 编译时的输出。

Parent #  0, Parameter 101
Parent # 11, Parameter 999

gcc-5.1编译错误

prog.cpp:29:17: error: template-id 'TObserve<0>' for 'void Thing::TObserve(Parameter)' does not match any template declaration
 template<> void Thing::TObserve<0>(Parameter p)
                 ^
prog.cpp:32:17: error: template-id 'TObserve<11>' for 'void Thing::TObserve(Parameter)' does not match any template declaration
 template<> void Thing::TObserve<11>(Parameter p)

编译 clang 3.7 中的错误。

prog.cpp:22:36: warning: 'override' keyword is a C++11 extension [-Wc++11-extensions]
        virtual void Observe(Parameter p) override
                                          ^
prog.cpp:29:24: error: no function template matches function template specialization 'TObserve'
template<> void Thing::TObserve<0>(Parameter p)
                       ^
prog.cpp:32:1: error: extraneous 'template<>' in declaration of variable 'TObserve'
template<> void Thing::TObserve<11>(Parameter p)
^~~~~~~~~~
prog.cpp:32:24: error: variable has incomplete type 'void'
template<> void Thing::TObserve<11>(Parameter p)
                       ^
prog.cpp:32:32: error: expected ';' at end of declaration
template<> void Thing::TObserve<11>(Parameter p)
                               ^
                               ;
prog.cpp:32:32: error: expected unqualified-id
1 warning and 5 errors generated.


[ 编辑 ]毕竟,它并不能真正vc++ 2015工作。似乎发生的情况是编译器允许void Thing::TObserve<0>定义,但在内部将其映射到void TaggedDispatch::TObserve<0>。如果添加另一个派生类,例如
class Other : public TaggedObserver<0>
{    };
template<> void Other::TObserve<0>(Parameter p)
{    cout << "Parent # 00, Parameter " << p << endl; }

然后vc++ 2015编译失败,并显示错误消息:

error C2766: explicit specialization; 'void TaggedDispatch::TObserve<0>(Parameter)' has already been defined

MSVC接受代码是错误的;Clang和GCC(和EDG)拒绝它是正确的。

这种情况与本问题中的案例类似,但它涉及不同的语法结构(以及编译器中的不同代码路径,产生不同的标准一致性结果,只有 EDG 是一致的)。

template<> void Thing::TObserve<0>(Parameter p)中,Thing::TObserve<0>是一个声明符 id,Thing::是一个嵌套名称说明符。[8.3p1] 说:

[...]当声明人ID合格时,声明应参考 到以前声明的类或命名空间的成员,其中 限定符引用(或者在命名空间的情况下,引用 该命名空间的内联命名空间集 (7.3.1)) 或专用化 其中;该成员不应仅由 由 指定的类或命名空间的范围内使用 声明 声明符 ID嵌套名称说明符。[...]

所以,你必须使用 template<> void TaggedDispatch::TObserve<0> .如问题中所述,使用 Thing:: 可能会造成一种错误的印象,即您可以为不同的派生类提供不同的显式TObserve专用化,但事实并非如此。只有一个TObserve成员函数模板,即在 TaggedDispatch 中声明的模板,并且所有此类显式专用化(以及隐式或显式实例化以及部分专用化)都"附加"到该声明。


使事情按预期方式工作的一种解决方案是在每个派生的类似Thing类中声明一个Observe成员函数模板,如有必要,可能会为相关Tag提供显式专用化,并允许模板的专用化使用 CRTP 自动连接到相应的Observer接口实例:

#include <iostream>
#include <cstddef>
using Parameter = int;
struct Observer 
{
   virtual void Observe(Parameter p) = 0;
};
template<std::size_t Tag> struct TaggedObserver : Observer { };
template<class Derived, std::size_t Tag> struct CrtpObserver : TaggedObserver<Tag>
{
   void Observe(Parameter p) override
   {
      static_cast<Derived*>(this)->template Observe<Tag>(p);
   }
};
struct Thing : CrtpObserver<Thing, 0>, CrtpObserver<Thing, 1>
{
   template<std::size_t N> void Observe(Parameter p);
};
template<> void Thing::Observe<0>(Parameter p)
{
   std::cout << "Interface #0, Parameter " << p << 'n';
}
template<> void Thing::Observe<1>(Parameter p)
{
   std::cout << "Interface #1, Parameter " << p << 'n';
}
int main()
{
   Thing test;
   TaggedObserver<0>* p0 = &test;
   TaggedObserver<1>* p1 = &test;
   p0->Observe(7);
   p1->Observe(3);
}

这会将 Observer 接口的实现放在它们所属Thing,同时在每个派生类中需要最少的管道 - 如果可以直接单独覆盖每个Observer::Observe,则无论如何都必须执行的操作。

最新更新