【问题标题】:case of template member function specialization that compiles on msvc, not others在 msvc 上编译的模板成员函数特化的情况,而不是其他
【发布时间】:2016-07-08 22:04:20
【问题描述】:

[ EDIT ] 我将标题从works 更改为compiles,因为事实证明它根本不起作用(感谢@bogdan 的cmets)。我在帖子末尾添加了代码,说明了原因和方法。

问题的第二部分仍然存在 - 有没有办法“修复”它?问题的关键在于将基类template<int N> class X 中的虚函数Observe 重新路由到派生自X<N> 的类中的模板函数Observe<N>,而无需X 中的任何支持代码。

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


在查看其他问题Choosing which base class to override method of 时,我发现以下 sn-p 在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.


[ EDIT ] 毕竟它在vc++ 2015 中并没有真正起作用。似乎发生的是编译器允许void Thing::TObserve&lt;0&gt; 定义,但在内部将其映射到void TaggedDispatch::TObserve&lt;0&gt;。如果添加另一个派生类,例如
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

【问题讨论】:

  • 好的,我将清理 cmets 并将信息移动到此处的答案中。这两个问题是相辅相成的,所以答案可以是一样的。
  • 完成。应该报告 MSVC 中的错误。我应该这样做,还是你想自己报告?
  • @bogdan 我同意这值得报告。如果你不介意继续做。谢谢。
  • MSVC 错误reported on Connect.
  • @bogdan。谢谢。在connect 上进行了复制和投票。

标签: c++ gcc visual-c++ clang language-lawyer


【解决方案1】:

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

这种情况与this question 中的情况相似,但它涉及不同的句法结构(以及编译器中的不同代码路径,产生不同的标准一致性结果,只有 EDG 是一致的)。

template&lt;&gt; void Thing::TObserve&lt;0&gt;(Parameter p) 中,Thing::TObserve&lt;0&gt; 是一个declarator-idThing:: 是一个嵌套名称说明符。 [8.3p1] 说:

[...] 当 declarator-id 符合条件时,声明应参考 到之前声明的类或命名空间的成员, 限定符指(或者,在命名空间的情况下,指 该命名空间的内联命名空间集(7.3.1))或专业化 其中;该成员不应仅由某位成员介绍 using-declaration 在由指定的类或命名空间范围内 declarator-idnested-name-specifier。 [...]

所以,你必须使用template&lt;&gt; void TaggedDispatch::TObserve&lt;0&gt;。如问题中所述,使用 Thing:: 可能会产生错误印象,即您可以为不同的派生类提供 TObserve 的不同显式特化,但事实并非如此。只有一个 TObserve 成员函数模板,在 TaggedDispatch 中声明的那个,所有这些显式特化(以及隐式或显式实例化和部分特化)都“附加”到该声明。


使事情按您期望的方式工作的一种解决方案是在每个派生的Thing-like 类中声明一个Observe 成员函数模板,如有必要,可能为相关的Tags 提供显式特化,并让特化模板使用 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,那么无论如何您必须做的事情不多直接地。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多