【问题标题】:CRTP-related compiler error on pointer-to-a-member-function default value指向成员函数默认值的 CRTP 相关编译器错误
【发布时间】:2011-11-19 04:28:43
【问题描述】:

你好,

在制作基于 CRTP 的通用包装器来调用任意库函数时,我遇到了一个我无法理解的问题。这是一个非常简化的代码来说明问题:

#include <iostream>

template< typename PValue, typename PDerived >
class TBase
{
 private:
  typedef TBase TSelf_;
  typedef PDerived TDerived_;

 protected:
  typedef PValue TValue_;

 protected:
  TBase( void )
  {
   std::cout << " TBase::TBase() " << std::endl;
  }

 public:
  void Foo( void )
  {
   std::cout << " TBase::Foo() " << std::endl;
  }

  template< typename PType >
  static void Call( PType /*pSomething*/, void(TDerived_::*pFunction)( void ) = &TSelf_::Foo, TDerived_ pDerived = TDerived_() )
  {
   ( pDerived.*pFunction )();
   std::cout << " static TBase::Call(). " << std::endl;
  }
};

template< typename PValue >
class TDerived : public TBase< PValue, TDerived< PValue > >
{
  friend class TBase< PValue, TDerived< PValue > > ;
 private:
  typedef TBase< PValue, TDerived > TBase_;
  typedef TDerived TSelf_;
 public:
  TDerived( void ) :
   TBase_()
  {
   std::cout << " TDerived::TDerived() " << std::endl;
  }
  void Foo( void )
  {
   std::cout << " TDerived::Foo() " << std::endl;
  }
  void Bar( void )
  {
   std::cout << " TDerived::Bar() " << std::endl;
  }
};

int main( void )
{
 TDerived< int >::Call( 1 );
 TDerived< int >::Call( 1, &TDerived< int >::Foo );
 TDerived< int >::Call( 1, &TDerived< int >::Bar, TDerived< int > () );
 return ( 0 );
}

一切都按预期编译和工作。但是,如果我尝试使用指向TDerived::Foo() 的指针作为TBase::Call(...) 中第二个参数的默认参数:

static void Call( PType /*pSomething*/, void(TDerived_::*pFunction)( void ) = &TDerived_::Foo, TDerived_ pDerived = TDerived_() )

compilers 给出了一个语法错误...我感觉它与编译器如何解析代码有关,并且它无法找出指向尚未定义(或实例化)类的函数的指针。但是,调用TDerived 构造函数作为TBase::Call(...) 的第三个参数的默认参数是没有问题的。有人可以就发生的事情给我一个明确的答案吗?为什么派生类 MFP 不被接受,而派生类的对象被接受为默认参数?

谢谢。

编辑:编译器错误(MSVS2010 命令行编译器):

FMain.cpp(224) : error C2061: syntax error : identifier 'TDerived_'; FMain.cpp(233) : see reference to class template instantiation 'TBase<PValue,PDerived> with [PValue=int,PDerived=TDerived<int>]' being compiled; FMain.cpp(323) : see reference to class template instantiation 'TDerived<PValue> with [PValue=int]' being compiled

这是一个语法错误 - 它无法将 TDerived_ 识别为 MFP 的默认参数类型。这之后还有其他错误,它们都是语法错误,因为函数定义现在格式错误。我是这么理解的。

编辑:基本上,我不明白为什么我可以使用TDerived_ 的对象作为默认参数,但不能使用指向成员函数的指针作为默认参数。

编辑:好的,这让我发疯了。 首先,我改成了typedef TBase&lt; PValue, TDerived &gt; TBase_;,正如有人指出的那样(谢谢大家!)。事实上,它只在 MSVC++ 下编译,因为这个编译器不做两部分解析;即,在 codepad.org(使用 g++ 4.1.2)上它没有编译。 其次,在那之后,我尝试在 codepad.org 上使用static void Call( PType /*pSomething*/, void(TDerived_::*pFunction)( void ) = &amp;TDerived_::Foo, TDerived_ pDerived = TDerived_() ) 并且......它编译并正确运行!所以我现在真的很困惑:人们向我解释了为什么它不正确(而且我无法理解“为什么”(参见我以前的编辑)),现在事实证明 g++ 可以正确编译它......这是否意味着它只是MSVC++ 问题而不是代码?或者从标准的角度来看,代码确实有问题(我看不到)并且 g++ “错误地”接受了它(我认为不太可能)?.. 帮助?!

【问题讨论】:

  • 编译器给出了什么错误?你为什么不也发布呢?是不是你要我们编译这段代码,自己看看错误?
  • “语法错误”错误消息可能是最没用的诊断消息(“某处有错误”除外)。
  • 回复:您最近的 cmets - 在 typedef 更改后对我来说代码看起来不错,但也许对标准有更广泛了解的人可以发表评论。 FWIW,更改后它会在 VS2008 和 GCC 4.2.1 上为我编译和运行。

标签: c++ templates crtp pointer-to-member


【解决方案1】:

TDerivedTBase_ 的 typedef 中类型的 TValue_ 参数的范围似乎是错误的 (!)

你有:

 private:
  typedef TBase< TValue_, TDerived > TBase_;

我认为你需要:

 private:
  typedef TBase< typename TBase< PValue, TDerived< PValue > >::TValue_, TDerived > TBase_;

甚至只是:

 private:
  typedef TBase< PValue, TDerived > TBase_;

编辑:C++ 标准第 14.6.2 节第 3 段涵盖了这种情况:

在类或类模板的定义中,如果是基类 取决于模板参数,不检查基类范围 在非限定名称查找期间,无论是在定义点 类模板或成员或在类的实例化期间 模板或成员

【讨论】:

  • TValue_TBase 中定义为受保护的typedefTDerived 公开继承自 TBase 并可以访问其受保护的定义。
  • @AzzA 实际上 msandiford 并没有说 TValue_ 在这种情况下是不可访问的,他说它没有被正确地“限定”。他的意思是TValue_ 在这里不可见
  • 关于为什么TValue_不能在派生类在C++中可见的解释,请参阅Using this keyword in destructor (closed)
  • @msandiford 和好奇的家伙:谢谢你的信息,伙计们!因此,显然,从标准的角度来看,代码是错误的(可能在很多方面),只是 MS 编译器接受它,因为没有两阶段名称查找。好吧,这很糟糕......
  • @AzzA MS 处理模板的方式与标准要求的完全不同(2 阶段名称查找)。在 C++ 模板的早期,模板的规则并不清楚(但如果你读过 D&E,你可以看到 BS 从一开始就想要类似 2 阶段名称查找),并且解析规则已经改变,特别是需要程序员添加typename 到处都是。但是这些现代模板规则在 1998 年就已经存在,所以如果 MS 明确选择不符合这里的标准。
【解决方案2】:

简单:在实例化TBase&lt; int, TDerived&lt; int&gt; &gt;模板类定义时,实例化Call&lt;&gt;函数模板的声明:

  template< typename PType >
  static void Call( PType , void(TDerived_::*pFunction)() = &TSelf_::Foo, TDerived_ pDerived = TDerived_() )

(与TDerived_ = TDerived&lt; int&gt;),这很好,因为此时声明了TSelf_::Foo()

OTOH,问题与

static void Call( PType , void(TDerived_::*pFunction)() = &TDerived_::Foo, TDerived_ pDerived = TDerived_() )

TDerived_::Foo()TBase&lt; int, TDerived&lt; int&gt; &gt;模板类定义实例化过程中没有声明。

顺便说一句,您不需要将参数列表指定为( void )() 具有相同的效果,并且不那么冗长。

【讨论】:

  • 是的,但是第三个默认参数呢?为什么我可以使用 'TDerived_' 临时作为第三个默认参数?如果我不能使用TDerived_ 成员函数的地址,如何在同一范围内创建TDerived_ 对象?我一定是真的很笨,但我还是不明白……
  • 我想,我需要一个符合标准的编译器。当编译器从根本上允许不应该根据标准编译的代码时,问“为什么编译而那个不编译”可能毫无意义。感谢您的意见,伙计们。
  • @AzzA "为什么我可以使用TDerived_temporary 作为第三个默认参数?" 知道TDerived_ 是一个类型,TDerived_() 的含义就知道了:构造一个TDerived_ 类型的临时对象。当然,只有一个构造函数可以接受 0 参数,并且在此上下文中可以访问,这一事实必须稍后进行检查,但这不会影响表达式为TDerived_ 类型的右值。 OTOH 编译TDerived_::Foo,编译器需要能够查找名称以确定表达式的类型。
  • (...) 但是这只是我的直觉。不要把它当作明确的陈述,或作为仔细的分析。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多