在msvc上编译模板成员函数专门化的情况,而不是其他 [英] case of template member function specialization that compiles on msvc, not others
问题描述
我将标题从工作
更改为编译
out它不真正工作毕竟(感谢@bogdan的意见)。我在帖子末尾添加了代码,显示了为什么和如何。
[ EDIT ] I changed the title from works
to compiles
since it turns out that it doesn't truly work after all (thanks @bogdan for the comments). I added code at the end of the post showing why and how.
问题的第二部分仍然存在 - 是否有办法 em>吗?问题的关键是在基础模板< int N>中有一个虚函数
Observe
在从 X
重新路由到模板化函数观察< N>
$ c>,而不需要 X
中的任何支持代码。
The second part of the question still stands - is there a way to "fix" it? The crux of the matter is having a virtual function Observe
in a base template<int N> class X
be rerouted to a templated function Observe<N>
in classes derived from X<N>
, without requiring any supporting code in X
.
有关如何通过要求 X
合作,请参阅此答案另一个问题基本上要求观察< N>
被声明到最导出的类中)。
查看这个问题选择哪个基类覆盖方法我发现下面的代码片段在
vc ++ 2015
(用 / W4 / Za
)并返回预期输出,但未能在 gcc-5.1
和 clang 3.7
(在 ideone.com 试用)。
For an example of how it can be done by requiring X
to cooperate see this answer to the other question (which basically requires that Observe<N>
be declared into the most derived class).
While looking at this other question Choosing which base class to override method of I found out that the following snippet compiles cleanly on
vc++ 2015
(with /W4 /Za
) and returns the expected output, yet fails to compile on gcc-5.1
and clang 3.7
(tried at ideone.com).
我知道模板函数的专业化有许多陷阱,但我仍然好奇C ++标准的哪个字母适用于这种情况,以及 - 在可能的情况下,代码不完全兼容 - 是否有一个简单的方法来修复 。
I am aware that specialization of template functions has many pitfalls, but I am still curious which letter of the C++ standard applies in this case, and - in the likely case that the code is not fully compliant - whether there is an easy way to "fix" it.
#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 。
Compile errors from 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>
。如果添加另一个派生类例如
[ EDIT ] It doesn't truly work in
vc++ 2015
after all. What appears to happen is that the compiler allows the void Thing::TObserve<0>
definition, but internally maps it to void TaggedDispatch::TObserve<0>
. This becomes obvious if adding another derived class e.g.
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错误接受代码; ang和GCC(和EDG)是正确的拒绝它。
MSVC is wrong to accept the code; Clang and GCC (and EDG) are right to reject it.
此案例与此问题中的类似,但它涉及一个不同的句法结构(和编译器中不同的代码路径,产生不同的标准一致性结果,只有EDG是一致的)。
This case is similar to the one in this question, but it involves a different syntactic construct (and different code paths in the compilers, yielding different Standard conformance results, with only EDG being consistent).
在 template<> void Thing :: TObserve< 0>(参数p)
, Thing :: TObserve< 0>
是 declarator-id , Thing ::
是 nested-name-specifier 。 [8.3p1]说:
In template<> void Thing::TObserve<0>(Parameter p)
, Thing::TObserve<0>
is a declarator-id, with Thing::
being a nested-name-specifier. [8.3p1] says:
[...]当 declarator-id 应将
引用到
限定符所引用的类或命名空间的先前声明的成员(或者在命名空间的情况下,是该命名空间的
内联命名空间集的元素(7.3.1))或专业化
;该成员不仅仅是由在
指定的类或命名空间范围内的
使用声明引入 nested-name-specifier 的 declarator-id 。 [...]
[...] When the declarator-id is qualified, the declaration shall refer to a previously declared member of the class or namespace to which the qualifier refers (or, in the case of a namespace, of an element of the inline namespace set of that namespace (7.3.1)) or to a specialization thereof; the member shall not merely have been introduced by a using-declaration in the scope of the class or namespace nominated by the nested-name-specifier of the declarator-id. [...]
所以,你必须使用 template< void TaggedDispatch :: TObserve< 0>
。如问题所述,使用 Thing ::
可能会创建一个错误的印象,您可以提供 TObserve
对于不同的派生类,情况并非如此。只有一个 TObserve
成员函数模板,在 TaggedDispatch
中声明的模板,以及所有这样的显式专门化
So, you have to use template<> void TaggedDispatch::TObserve<0>
. As noted in the question, using Thing::
could create the false impression that you can provide different explicit specializations of TObserve
for different derived classes, which is not the case. There's only one TObserve
member function template, the one declared in TaggedDispatch
, and all such explicit specializations (and implicit or explicit instantiations, and partial specializations) are "attached" to that declaration.
一个解决方案,使事情按照你期望的方式工作是在每个派生的 Thing
-like类中声明一个观察
成员函数模板,可能提供相关< c $ c> Tag s,并且使用CRTP将模板的特化自动连接到相应的 Observer
接口实例: p>
One solution to make things work the way you expect is to declare an Observe
member function template in each derived Thing
-like class, possibly providing explicit specializations for relevant Tag
s if necessary, and let specializations of the template be automatically wired up to the corresponding Observer
interface instance using CRTP:
#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
。
This places the implementations of the Observer
interface in Thing
where they belong, while requiring minimal plumbing in each derived class - not much more than what you would have to do anyway if you could separately override each Observer::Observe
directly.
这篇关于在msvc上编译模板成员函数专门化的情况,而不是其他的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!