[编辑] 我将标题从works
更改为compiles
,因为事实证明它毕竟不起作用(感谢@bogdan for the评论)。我在帖子的末尾添加了代码,说明原因和方法。
问题的第二部分仍然存在 - 是否可以通过<#34; 修复&#34;它?问题的关键是在基础Observe
中将虚拟函数template<int N> class X
重新路由到从Observe<N>
派生的类中的模板化函数X<N>
,而不需要任何支持代码X
。
有关如何通过要求X
进行合作来完成此操作的示例,请参阅this answer与其他问题(基本上要求将Observe<N>
声明为最派生的类)。
vc++ 2015
(使用/W4 /Za
)上干净地编译并返回预期的输出,但无法在gcc-5.1
上编译和clang 3.7
(在ideone.com处尝试过)。
我知道模板函数的专业化有许多缺陷,但我仍然很好奇C ++标准的哪个字母适用于这种情况,并且 - 在可能的情况下代码不完全符合 - 是否有一个简单的通往&#34;修复&#34;它
#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
答案 0 :(得分:1)
MSVC接受代码是错误的; Clang和GCC(和EDG)拒绝它是正确的。
这种情况类似于this question中的情况,但它涉及不同的句法结构(编译器中的不同代码路径,产生不同的标准一致性结果,只有EDG一致)。
在template<> void Thing::TObserve<0>(Parameter p)
中,Thing::TObserve<0>
是声明者ID ,Thing::
是嵌套名称说明符。 [8.3p1]说:
[...]当 declarator-id 合格时,声明应参考 到以前声明的类或命名空间的成员 限定符指的是(或者,在命名空间的情况下,指的是元素的元素) 该命名空间的内联命名空间集(7.3.1))或特化 物;该成员不仅仅是由一个人介绍的 using-declaration 在由类提名的类或命名空间的范围内 declarator-id 的嵌套名称说明符。 [...]
因此,您必须使用template<> void TaggedDispatch::TObserve<0>
。如问题中所述,使用Thing::
可能会产生错误的印象,即您可以为不同的派生类提供TObserve
的不同显式特化,但实际情况并非如此。只有一个TObserve
成员函数模板,在TaggedDispatch
中声明的模板,所有这些显式特化(以及隐式或显式实例化和部分特化)都“附加”到该声明。
使事情按预期方式工作的一种解决方案是在每个派生的Observe
类中声明Thing
成员函数模板,可能为相关的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
直接。