C++11中使用指定模板参数的make_pair无法编译通过。

87

我刚刚尝试使用启用了-std=c++11选项的g++ 4.7(较新的快照之一)进行实验。我尝试编译我的一些现有代码库,但其中一个失败的案例让我有些困惑。

如果有人能够解释一下发生了什么,我将不胜感激。

这是代码:

#include <utility>
#include <iostream>
#include <vector>
#include <string>

int main ( )
{
    std::string s = "abc";

    // 1 ok
    std::pair < std::string, int > a = std::make_pair ( s, 7 );

    // 2 error on the next line
    std::pair < std::string, int > b = std::make_pair < std::string, int > ( s, 7 );

    // 3 ok
    std::pair < std::string, int > d = std::pair < std::string, int > ( s, 7 );

    return 0;
}

我理解make_pair应该用作(1)情况,如果我指定了类型,那我也可以使用(3),但我不明白为什么在这种情况下它会失败。

具体错误如下:

test.cpp: In function ‘int main()’:
    test.cpp:11:83: error: no matching function for call to ‘make_pair(std::string&, int)’
    test.cpp:11:83: note: candidate is:
    In file included from /gcc4.7/usr/local/lib/gcc/i686-pc-linux-gnu/4.7.0/../../../../include/c++/4.7.0/utility:72:0,
                 from test.cpp:1:
    /gcc4.7/usr/local/lib/gcc/i686-pc-linux-gnu/4.7.0/../../../../include/c++/4.7.0/bits/stl_pair.h:274:5:
note: template<class _T1, class _T2> constexpr std::pair<typename std::__decay_and_strip<_T1>::__type, typename std::__decay_and_strip<_T2>::__type> std::make_pair(_T1&&, _T2&&)
    /gcc4.7/usr/local/lib/gcc/i686-pc-linux-gnu/4.7.0/../../../../include/c++/4.7.0/bits/stl_pair.h:274:5:
note:   template argument deduction/substitution failed:
    test.cpp:11:83: note:   cannot convert ‘s’ (type ‘std::string {aka std::basic_string<char>}’) to type ‘std::basic_string<char>&&’

这里的问题仅限于“发生了什么?”我知道可以通过删除模板规范来解决问题,但我只想知道在幕后失败的原因。

  • g++ 4.4编译此代码没有任何问题。
  • 删除-std=c++11也可以编译此代码并且没有任何问题。

7
一个非常棒的问题。这是 C++11 中另一个微妙的破坏性变更的例子,类似于 std::vector 构造函数中的破坏性变更。至少这个变更会产生编译器错误,而不是在语义上默默地改变。 - James McNellis
1
如果我有一个整数变量i,我想用它和另一个对象组成一对。那么调用makepair应该怎么做呢?是1) make_pair<*i, obj> 2) int&& j = i; make_pair<j, obj>?但这两种方法都不可行。正确的做法是什么? - PHcoDer
1个回答

143
这不是 std::make_pair 的正确使用方式;您不应该显式指定模板参数。
C++11 的 std::make_pair 接受两个参数,类型为T&&U&&,其中TU是模板类型参数。实际上,它看起来像这样(忽略返回类型):
template <typename T, typename U>
[return type] make_pair(T&& argT, U&& argU);

当您调用std::make_pair并明确指定模板类型参数时,不会进行任何参数推导。相反,类型参数直接替换到模板声明中,产生以下结果:
[return type] make_pair(std::string&& argT, int&& argU);

请注意,这两种参数类型都是右值引用。因此,它们只能绑定到右值。对于您传递的第二个参数 7,这不是问题,因为它是一个右值表达式。然而,s是一个左值表达式(它不是临时的,也没有被移动)。这意味着函数模板不适用于您的参数,这就是为什么会出现错误的原因。
那么,为什么在模板参数列表中不明确指定 TU 时它能正常工作呢?简而言之,在模板中,右值引用参数是特殊的。部分原因是由于一种称为reference collapsing的语言特性,其中类型为A&&的右值引用参数,其中A是模板类型参数,可以绑定到任何类型的A
无论 A 是左值、右值、带有 const 限定符、带有 volatile 限定符或没有限定符,只要 A 本身是模板参数,A&& 就可以绑定到该对象。(如果且仅如果 A 本身是模板参数)。
在您的示例中,我们进行调用:
make_pair(s, 7)

这里,s 是类型为std::string的左值,7是类型为int的右值。由于没有指定函数模板的模板参数,将会执行模板参数推导以确定参数。

为了将左值s绑定到T&&上,编译器会推导Tstd::string&,得到一个参数类型为std::string& &&的结果。尽管存在"双重引用",但这里不会出现引用的引用,因此最终结果变成了std::string&s匹配成功。

7绑定到U&&很简单:编译器会推导Uint,从而得到一个类型为int&&的参数,可以成功地绑定到7,因为它是一个右值。

这些新的语言特性存在许多微妙之处,但如果遵循一个简单的规则,就会非常容易:

如果函数参数可以推导出模板参数,请让它自动推导。除非你必须要提供这个参数,否则不要显式地提供它。让编译器来做繁重的工作,99.9% 的情况下,它都会得到你想要的结果。当它不是你想要的时候,通常会得到一个易于识别和修复的编译错误。

6
这是一个非常好的和全面的解释。谢谢! - vmpstr
1
@James - 那个“一个简单的规则”是来自另一篇文章或答案吗?我应该阅读吗? - Michael Burr
4
@MichaelBurr: 哈哈,那只是我编的。希望那是真的!我认为它是真的......这个规则几乎总是适用于我。 - James McNellis
1
@James:谢谢。它周围的“引用框”让我觉得它可能是在其他地方原先写的东西。这个答案真的很有启发性,我只是想确保我没有错过其他地方的什么东西。 - Michael Burr
2
这也适用于元组吗? - Ferruccio
显示剩余2条评论

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