GCC4 模板错误或更可能的 ID10t 错误

gcc4 template bug or more likely id10t error

本文关键字:错误 ID10t GCC4      更新时间:2023-10-16

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

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]

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

简而言之,如果模板函数的参数

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

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

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

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

小一:

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

小二:

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

该标准规定,对于每个模板函数(T1(,我们必须为其每个模板参数泛型唯一类型,使用这些类型来确定函数调用参数类型,然后使用这些类型推导出其他模板(T2(中的类型。如果此操作成功,则第一个模板 (T1( 至少与第二个模板 (T2( 一样专用。第一个小一至小一>

小一
  1. 合成 P2 模板参数Packager的唯一类型U
  2. P1 的参数列表执行类型推断。 Packager被推导出为UT被推导出为AddPkgrConst<Packager,U>::type

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

现在 P1->P2:

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

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

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

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

在我们的类型推导中,推导的 A 是 AddPkgrConst<U1,bool>::type = bool 。这与原始 A 不兼容,原始 A 是唯一类型 U2 .这似乎支持了偏序解决歧义的立场。

我不知道

Visual Studio有什么问题,但gcc说的似乎是对的:

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

由于函数模板不能专用化,因此此处有两个函数模板重载。这两个重载都能够接受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.
}