5

以下代码在 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]
4

3 回答 3

4

这对我来说似乎是一个编译器错误。这里涉及的问题是重载解析和模板函数的部分排序。由于两个模板函数都可以匹配参数列表(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. 为 P2U的模板参数合成唯一类型。Packager
  2. P1对的参数列表进行类型推导。Packager被推断为UT被推断为AddPkgrConst<Packager,U>::type.

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

现在 P1->P2:

  1. 为模板参数和P1合成唯一类型U1和以获得参数列表(U1*,U2*)。U2PackagerT
  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 不兼容,这是唯一的类型U2。这似乎支持部分排序​​解决歧义的立场。

于 2012-01-27T21:30:48.833 回答
3

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

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

于 2012-01-27T18:22:56.487 回答
1

由于函数模板不能特化,所以这里有两个函数模板重载。这两个重载都能够接受 abool*作为它们的第二个参数,因此它们似乎被 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.
}
于 2012-01-27T18:59:46.873 回答