将字符串字面值作为const char*的引用传递无法在g++ 4.6.3中编译通过。

7

这是来自于C++ Primer, 4th edition, Chapter 16的一个例子,它涉及到模板特化

template <class T>
int compare(const T& v1, const T& v2) {
    if(v1 < v2) return -1;
    if(v2 < v1) return 1;
    return 0;
}

template <>
int compare<const char*>(const char* const &v1, const char* const &v2){
    return strcmp(v1, v2);
}

int main(int argc, const char *argv[])
{
    cout << compare("abc", "defg") << endl;
    return 0;
}

我希望compare("abc", "defg")会调用模板的专门版本。 但事实是,g++ 4.6.3无法编译此代码并给出以下错误:

error: no matching function for call to 'compare(const char [4], const char [5])'

note: candidate is: template int compare(const T&, const T&)

现在考虑以下事实:

I. 字符串字面值,在C++中被称为C风格字符串,实际上是一个const char数组

II. 如果作为普通非引用类型传递,数组将被静默地转换为指向其第一个元素的指针。

我只是将字符串字面值"abc"和"defg"作为const char*的引用传递,并期望它们首先被转换为const char*,然后通过引用传递。 但似乎g++不同意我的想法,拒绝编译代码。
但如果我用函数重载替换模板特化,也就是替换:
template <>
int compare<const char*>(const char* const &v1, const char* const &v2){
    return strcmp(v1 ,v2);
}

使用:

int compare(const char* const& v1, const char* const& v2){
    return strcmp(v1, v2);
}

那么问题出现在哪里呢? 为什么我不能通过参数类型const char * const&在模板专业化版本中传递字符串字面量呢?

然后g++就可以愉快的编译了。


你的第二个比较重载看起来和第一个一样 - 都使用 const char * - 是否需要修改? - Scott Jones
然后编译再次失败 - 在我的电脑上使用clang和g++都可以。 - awesoon
"abc","defg" 不是 const char* - Avraam Mavridis
@soon 我再次尝试了"然后编译失败",这次有效了...问题已编辑。 - ltux
2个回答

6
下面的答案基于《C++模板:完全指南》第57页的解释:使用字符串字面值作为函数模板的参数。
template <class T>
int compare(const T& v1, const T& v2) 
{
    if (v1 < v2) return -1;
    if (v2 < v1) return 1;
    return 0;
}

这需要两个参数 v1v2 具有相同的类型。

template <>
int compare<const char*>(const char* const &v1, const char* const &v2){
    return strcmp(v1, v2);
}

这需要您拥有参数的const char *类型。

然而,"abc"的类型为char const[4],而"defg"的类型为char const[5]。它们是不同的类型。由于专门化和模板化版本都需要引用参数,因此在参数推断过程中没有数组到指针的衰减。因此,您不能向它们传递不同长度的字符串字面值以查找匹配项。如果您提供一个常规函数,它不需要任何参数推断,编译器将找到匹配项。

如果声明非引用参数,则可以使用不同长度的字符串字面值替换它们。这种行为的原因是,在参数推断期间,仅当参数没有引用类型时才会发生array-to-pointer转换(通常称为衰减)。


感谢您的回答。只是我想补充一点,strcmp返回一个差异值。如果我们想要匹配示例,则应执行以下操作:return compare(strcmp(a, b), 0); - aloisdg

4

模板特化不参与重载决议过程。只有主模板被重载决议考虑。

模板特化只在后面才会发挥作用,并且仅在它们的主模板“获胜”重载决策时才会发挥作用。也就是说,模板特化在“特化”的过程中使用,它们在重载决策过程中完全不可见。

因此,在您的第一个示例中,只有一个候选项被重载决议考虑。

template <class T> int compare(const T& v1, const T& v2);

为了成功,这个候选人需要通过模板参数推断来确定你的一组参数。(模板参数推断过程不关心任何其他特化。)在这种情况下,由于数组类型的参数模板参数T被推断为一个数组,导致模板参数推断失败。并且您得到两个参数的不兼容的推论。编译器给出了描述问题的错误消息。换句话说,在第一个示例中,模板的专业版本永远没有机会发挥作用。
在您的第二个示例中,您用重载替换了专业化,并为重载解决提供了第二个候选项。现在编译器看到了两者。
template <class T> int compare(const T& v1, const T& v2);
int compare(const char* const& v1, const char* const& v2);

模板候选者与之前一样失败,而重载候选者成功。

为了更好地说明在这种情况下模板特化的工作原理,我们可以参考您的原始代码并更改主要模板,以使其通过解耦参数来通过重载分辨过程。如果在您的第一个示例中,您将模板声明更改为

template <class T1, class T2>
int compare(const T1& v1, const T2& v2) {
  ...

只要其他部分保持不变,代码就会编译,但它会使用你的特化。但即使在这种情况下,具有推断参数的主模板仍将被视为与您的参数更匹配(立即引用绑定而无需转换)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接