【问题标题】:gcc4 template bug or more likely id10t errorgcc4 模板错误或更可能的 id10t 错误
【发布时间】:2012-01-27 18:06:22
【问题描述】:

以下代码在 Visual Studio 下编译得很好,但 gcc 4.6.2 或 4.7 都无法处理它。它似乎是有效的,但 gcc 似乎无法解决 const 和非 const 参数之间的区别。这可能是编译器错误吗?

struct CReadType{};
struct CWriteType{};

template<typename ReadWriteType, typename T> 
struct AddPkgrConstByType {}; 
template<typename T> 
struct AddPkgrConstByType<CReadType, T> {
   typedef T type;
};    
template<typename T>
struct AddPkgrConstByType<CReadType, const T> {
    typedef T type;
};
template<typename T>
struct AddPkgrConstByType<CWriteType, T> {
    typedef T const type;
};

template<typename Packager, typename T>
struct AddPkgrConst : public AddPkgrConstByType<typename Packager::CReadWriteType, T> {
};

template<typename Packager, typename T>
inline bool Package( Packager* ppkgr, T* pt ) 
{
    return true;
}

template<typename Packager>
inline bool Package( Packager* ppkgr, typename AddPkgrConst<Packager,bool>::type* pb) 
{
    return false;
}

struct ReadPackager {
    typedef CReadType CReadWriteType;
};
struct WritePackager {
    typedef CWriteType CReadWriteType;
};

int main(int argc, char* argv[])
{
    ReadPackager rp;
    WritePackager wp;
    bool b = true;
    const bool cb = false;
    Package( &rp, &b );
}

编译器调用:

g++ -fPIC -O -std=c++0x -Wno-deprecated -D_REENTRANT 
g++-D__STDC_LIMIT_MACROS -c test.cpp
test.cpp: In function ‘int main(int, char**)’:
test.cpp:58:22: error: call of overloaded ‘Package(ReadPackager*, bool*)’ is ambiguous
test.cpp:58:22: note: candidates are:
test.cpp:31:6: note: bool Package(Packager*, T*) [with Packager = ReadPackager, T = bool]
test.cpp:38:6: note: bool Package(Packager*, typename AddPkgrConst<Packager, bool>::type*) [with Packager = ReadPackager, typename AddPkgrConst<Packager, bool>::type = bool]

【问题讨论】:

  • +1 获取完整的最小示例程序
  • @Mr.Anubis :标准规定了重载解析的工作方式,因此它不应该依赖于编译器。
  • @ildjarn 我弄错了,谢谢 :)
  • @Mr.Anubis :我不确定——你说“如果你有第二个参数类型指针指向typename AddPkgrConst&lt;Packager, bool&gt;::type,那么重载可能会选择第二个 " 但问题是,在这种情况下,typename AddPkgrConst&lt;Packager, bool&gt;::type 解析为 bool,这 正在传递的内容,所以我对你得到的内容感到困惑。

标签: c++ templates gcc compiler-construction gcc4


【解决方案1】:

对我来说,这看起来像是一个编译器错误。这里涉及的问题是重载解析和模板函数的部分排序。由于两个模板函数都可以匹配参数列表(ReadPackager*, bool),因此应该使用模板函数的部分排序来选择更专业的模板函数。

简而言之,如果模板函数的参数始终可以用作另一个模板函数的参数,则该模板函数至少与另一个函数一样专用。

很明显,任何两个指针参数都匹配第一个 Package() 函数,但例如 Package(ReadPackager*, const int*) 不能匹配第二个。这似乎意味着第二个 Package 函数更专业,应该解决任何歧义。

但是,由于编译器之间存在分歧,因此可能会涉及一些细微之处,而简化解释会忽略这些细微之处。因此,我将按照从标准中确定函数模板部分排序的过程来辨别正确的行为。

首先,将函数标记为 P1 和 P2 以方便参考。

P1:

template<typename Packager, typename T>
bool Package( Packager* ppkgr, T* pt );

P2:

template<typename Packager>
bool Package( Packager* ppkgr, typename AddPkgrConst<Packager,bool>::type* pb);

标准规定,对于每个模板函数(T1),我们必须为其每个模板参数泛型唯一类型,使用这些类型确定函数调用参数类型,然后使用这些类型推导出另一个类型模板(T2)。如果成功,第一个模板 (T1) 至少与第二个模板 (T2) 一样特化。 第一个P2->P1

  1. 为P2的模板参数Packager合成唯一类型U
  2. P1的参数列表进行类型推导。 Packager 被推断为UT 被推断为AddPkgrConst&lt;Packager,U&gt;::type

这成功了,P1 被判断为不比 P2 更专业。

现在 P1->P2:

  1. 为P1的模板参数PackagerT合成唯一类型U1U2,得到参数列表(U1*,U2*)。
  2. P2的参数列表进行类型推导。 Packager 被推断为 U1。
  3. 不会对第二个参数执行推导,因为作为依赖类型,它被视为非推导上下文。
  4. 因此,第二个参数是AddPkgrConst&lt;U1,bool&gt;::type,其计算结果为bool。这与第二个参数U2 不匹配。

如果我们继续执行第 4 步,此过程将失败。但是,我怀疑拒绝此代码的编译器不会执行第 4 步,因此仅因为类型推导成功,所以认为 P2 不比 P1 更专业。这似乎违反直觉,因为 P1 清楚地接受 P2 所做的任何输入,反之亦然。这部分标准有些复杂,所以不清楚是否需要进行最后的比较。

让我们尝试通过应用 §14.8.2.5,第 1 段,从类型推导模板参数来解决这个问题

可以在几种不同的上下文中推导出模板参数,但在每种情况下,都会将根据模板参数指定的类型(称为 P)与实际类型(称为 A)进行比较,并尝试查找模板参数值(类型参数的类型、非类型参数的值或模板参数的模板),在替换推导值(称为推导 A)后,使 P 兼容A.

在我们的类型推导中,推导出的 A 是AddPkgrConst&lt;U1,bool&gt;::type=bool。这与原始 A 不兼容,即唯一类型 U2。这似乎支持了部分排序解决歧义的立场。

【讨论】:

  • 出错的是 VC++,而不是 GCC。他的代码解析为Package(ReadPackager*, bool*),这是非常模棱两可的。
  • 模板歧义应该使用模板偏序来解决。该标准描述了确定一个模板是否比另一个模板更专业所涉及的相当复杂的过程。例如,“template Package(Packager*, bool*)” 也将采用与现有函数相同的参数,但 gcc 和其他编译器正确地确定它比第一个模板更专业,并且不会发出错误。
  • 确实如此,但这无关紧要。 Package(Packager*, typename AddPkgrConst&lt;Packager, bool&gt;::type*) 并不比 Package(Packager*, T*) 更专业,只是因为它使用了元函数;在这种情况下,AddPkgrConst&lt;Packager, bool&gt;::typeT 都解析为 bool。另一方面,Package(Packager*, bool*) 更专业。 (要清楚,它无关紧要的原因是因为AddPkgrConst&lt;Packager, bool&gt;::type中的Packager是一个依赖类型。)
  • 我在原始评论中解释了为什么我认为第二个 Package 函数比第一个更专业。没有魔法,它与依赖类型无关。如果您能解释标准中描述的部分排序的哪一部分使第二个函数不比第一个函数更专业,那将很有帮助。
  • "我在原始评论中解释了为什么我认为第二个 Package 函数比第一个更专业。" 不,你没有,你只是一直提到模板偏序. C++11 §14.5.6.2/3:“要生成转换后的模板,对于每个类型、非类型或模板模板参数(包括其中的模板参数包(14.5.3))合成一个唯一类型,值或类模板,并将其替换为模板函数类型中该参数的每次出现。"(续)
【解决方案2】:

我不知道 Visual Studio 出了什么问题,但 gcc 说的似乎是对的:

您实例化AddPkgrConstByType&lt;CReadType, T&gt;,因为Packager::CReadWriteType 解析为CReadType。因此,AddPkgrConst&lt;Packager,bool&gt;::type 将根据第一个实现(不是专业化)解析为bool。这意味着您有两个具有相同参数列表的独立函数特化,而 C++ 不允许这样做。

【讨论】:

  • AddPkgrConst&lt;Packager,bool&gt;::type 在此上下文中不使用任何特化,因为&lt;CReadType, bool&gt; 没有特化(仅&lt;CReadType, bool const&gt;)。因此,改为使用通用(非专业)定义,它也只是简单地做typedef T type
  • @ildjarn:谢谢,我实际上是指第一个实现,而是写了 specialisation。我会解决的。
  • 实际上使用的是部分专业化。
  • @R.MartinhoFernandes :啊,你是对的;由于格式不佳(Mooing Duck 已修复),我错过了这一点。
【解决方案3】:

由于函数模板不能被特化,所以这里有两个函数模板重载。这两个重载都能够接受 bool* 作为它们的第二个参数,因此它们似乎被 g++ 正确地检测为模棱两可。

然而类可以被部分特化,这样只会选择一个版本,你可以使用包装魔法来达到你想要的目标。我只是粘贴我添加的代码。

template <typename Packager, typename T>
struct Wrapper
{
    static bool Package()
    {
        return true;
    }
};

template <typename Packager>
struct Wrapper<Packager, typename AddPkgrConst<Packager,bool>::type>
{
    static bool Package()
    {
        return false;
    }
};

template <typename Packager, typename T>
Wrapper<Packager, T> make_wrapper(Packager* /*p*/, T* /*t*/)
{
    return Wrapper<Packager, T>();
}

int main()
{
    ReadPackager rp;
    bool b = true;
    std::cout << make_wrapper(&rp, &b).Package() << std::endl;  // Prints out 0.
}

【讨论】:

  • AddPkgrConst 是一个具有部分特化的结构,在这种情况下执行包装。 const 特化应该足以生成一个唯一类型,即使它去除了 typedef T type 中的 const。这应该仍然足以毫无歧义地解决函数重载。
猜你喜欢
  • 2011-01-29
  • 2015-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-29
  • 2013-05-04
相关资源
最近更新 更多