c++ - 在 msvc 上编译的模板成员函数特化的情况,而不是其他

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

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

问题的第二部分仍然存在 - 是否有办法“修复”它?问题的症结在于有一个虚函数 Observe在一个基地template<int N> class X被重新路由到模板化函数 Observe<N>在派生自 X<N> 的类中,不需要 X 中的任何支持代码.

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


在查看另一个问题时 Choosing which base class to override method of我发现以下代码片段可以在 vc++ 2015 上干净地编译(使用 /W4 /Za )并返回预期的输出,但无法在 gcc-5.1 上编译和 clang 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)拒绝它是正确的。

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

template<> void Thing::TObserve<0>(Parameter p) , Thing::TObserve<0>是一个declarator-id,带有Thing::作为一个嵌套名称说明符。 [8.3p1] 说:

[...] 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 中声明的那个,以及所有此类显式特化(以及隐式或显式实例化,以及部分特化)都“附加”到该声明。


使事情按照您期望的方式工作的一个解决方案是声明一个 Observe每个派生中的成员函数模板Thing -like 类,可能为相关的 Tag 提供明确的特化s 如果需要,让模板的特化自动连接到相应的 Observer使用 CRTP 的接口(interface)实例:

#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,那么无论如何您都必须做的事情直接地。

关于c++ - 在 msvc 上编译的模板成员函数特化的情况,而不是其他,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38276005/

相关文章:

c++ - 未找到 macOS Clang C++17 文件系统 header

c++ - 这个 OpenCV 矩阵表达式的含义是什么?

c++ - std::priority_queue 中具有相同优先级的元素的顺序

c++ - 在 win7 上使用 MinGW 6.3.0 构建 boost 1.63.0

c - Makefile,clang OK,gcc错误

c++ - strtod 和下溢

c++ - 可以通过模板间接访问基类中的私有(private)类型

c++ - 为什么通常的访问控制检查适用于通过模板参数访问时用于指定显式实例化的名称?

c - 为什么编译器会生成这段代码?

c++ - 如何在 KeyEventArgs...VC++2010 中一起处理两个函数?