C ++ 11 make_pair พร้อมพารามิเตอร์เทมเพลตที่ระบุจะไม่คอมไพล์


85

ฉันเพิ่งเล่นกับ g ++ 4.7 (หนึ่งในสแน็ปช็อตในภายหลัง) ที่เปิดใช้งาน -std = c ++ 11 ฉันพยายามรวบรวมฐานรหัสที่มีอยู่บางส่วนและมีกรณีหนึ่งที่ล้มเหลวค่อนข้างทำให้ฉันสับสน

ฉันจะขอบคุณถ้ามีใครสามารถอธิบายสิ่งที่เกิดขึ้นได้

นี่คือรหัส:

#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 ยังคอมไพล์ด้วยโค้ดโดยไม่มีปัญหา

6
คำถามที่ยอดเยี่ยม อีกตัวอย่างหนึ่งของการเปลี่ยนแปลงการทำลายอย่างละเอียดใน C ++ 11 ซึ่งคล้ายกับการเปลี่ยนแปลงstd::vectorโครงสร้างอย่างสิ้นเชิง อย่างน้อยข้อนี้ก็ให้ข้อผิดพลาดของคอมไพเลอร์และไม่ใช่การเปลี่ยนแปลงความหมายแบบไม่โต้ตอบ
James McNellis

1
ถ้าฉันมีตัวแปรจำนวนเต็ม i. ฉันต้องการจับคู่กับ i และวัตถุอื่น ฉันจะเรียก makepair ได้อย่างไร 1) make_pair <* i, obj> 2) int && j = i; make_pair <j, obj>? ทั้งสองไม่ทำงาน วิธีที่ถูกต้องในการทำคืออะไร?
PHcoDer

คำตอบ:


136

นี่ไม่ใช่วิธีstd::make_pairการใช้งาน คุณไม่ควรระบุอาร์กิวเมนต์ของเทมเพลตอย่างชัดเจน

C ++ 11 std::make_pairรับสองอาร์กิวเมนต์ประเภทT&&และU&&โดยที่TและUเป็นพารามิเตอร์ประเภทเทมเพลต อย่างมีประสิทธิภาพดูเหมือนว่านี้ (โดยไม่สนใจประเภทผลตอบแทน):

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);

โปรดทราบว่าพารามิเตอร์ทั้งสองประเภทนี้เป็นการอ้างอิง rvalue ดังนั้นพวกเขาสามารถผูกกับ rvalues ​​เท่านั้น นี่ไม่ใช่ปัญหาสำหรับอาร์กิวเมนต์ที่สองที่คุณส่งผ่าน7เพราะนั่นคือนิพจน์ rvalue sอย่างไรก็ตามเป็นนิพจน์ lvalue (ไม่ใช่ชั่วคราวและไม่มีการเคลื่อนย้าย) ซึ่งหมายความว่าเทมเพลตฟังก์ชันไม่ตรงกับอาร์กิวเมนต์ของคุณซึ่งเป็นสาเหตุที่ทำให้คุณได้รับข้อผิดพลาด

เหตุใดจึงใช้งานได้เมื่อคุณไม่ระบุสิ่งที่ชัดเจนTและUอยู่ในรายการอาร์กิวเมนต์ของเทมเพลต กล่าวโดยย่อพารามิเตอร์อ้างอิง rvalue มีความพิเศษในเทมเพลต เนื่องจากในส่วนที่มีคุณลักษณะเป็นภาษาที่เรียกว่าการอ้างอิงยุบ , พารามิเตอร์อ้างอิง rvalue ประเภทA&&ที่เป็นพารามิเตอร์ชนิดแม่สามารถผูกกับชนิดของใดAA

ไม่สำคัญว่าค่าAนั้นจะเป็น lvalue, rvalue, const-qual, volatile-qualification หรือไม่มีเงื่อนไขA&&สามารถผูกกับออบเจ็กต์นั้นได้ (อีกครั้งถ้าAเป็นพารามิเตอร์ template)

ในตัวอย่างของคุณเราทำการโทร:

make_pair(s, 7)

นี่sเป็น lvalue ชนิดstd::stringและ7เป็น rvalue intประเภท เนื่องจากคุณไม่ได้ระบุอาร์กิวเมนต์แม่แบบสำหรับเทมเพลตฟังก์ชันการหักอาร์กิวเมนต์แม่แบบจะดำเนินการเพื่อหาว่าอาร์กิวเมนต์คืออะไร

จะผูกsเป็น lvalue เพื่อT&&, ฉงนฉงายคอมไพเลอร์Tที่จะยอมข้อโต้แย้งของพิมพ์std::string& std::string& &&มีการอ้างอิงถึงการอ้างอิงไม่มีมี แต่ดังนั้นนี้อ้างอิง "คู่" std::string&พังทลายลงมาจะกลายเป็น sเป็นการแข่งขัน

เป็นเรื่องง่ายที่จะผูก7เข้ากับU&&: คอมไพเลอร์สามารถอนุมานได้Uว่าเป็นintโดยให้พารามิเตอร์ประเภทint&&ซึ่งผูกได้สำเร็จ7เพราะเป็นค่า rvalue

มีรายละเอียดปลีกย่อยมากมายพร้อมคุณสมบัติภาษาใหม่เหล่านี้ แต่ถ้าคุณทำตามกฎง่ายๆข้อเดียวมันก็ค่อนข้างง่าย:

หากอาร์กิวเมนต์แม่แบบสามารถอนุมานได้จากอาร์กิวเมนต์ของฟังก์ชันให้อนุมานได้ อย่าระบุข้อโต้แย้งอย่างชัดเจนเว้นแต่คุณจะต้องระบุอย่างชัดเจน

ปล่อยให้คอมไพเลอร์ทำงานหนักและ 99.9% ของเวลานั้นก็จะเป็นอย่างที่คุณต้องการอยู่ดี เมื่อไม่ใช่สิ่งที่คุณต้องการคุณมักจะได้รับข้อผิดพลาดในการคอมไพล์ซึ่งง่ายต่อการระบุและแก้ไข


6
นี่เป็นคำอธิบายที่ดีและครอบคลุมมาก ขอขอบคุณ!
vmpstr

1
@ เจมส์ - นั่นคือ "กฎง่ายๆข้อเดียว" จากบทความอื่นหรือคำตอบที่ฉันควรอ่าน?
Michael Burr

4
@MichaelBurr: Nah ฉันเพิ่งสร้างมันขึ้นมา :-) ดังนั้นฉันหวังว่ามันจะเป็นจริง! ฉันคิดว่ามันเป็นเรื่องจริง ... กฎนั้นใช้ได้ผลกับฉันตลอดเวลา
James McNellis

1
@ เจมส์: ขอบคุณ 'กล่องคำพูด' ที่อยู่รอบ ๆ มันทำให้ฉันคิดว่ามันอาจเป็นสิ่งที่เขียนไว้ที่อื่น คำตอบนี้ให้ข้อมูลจริงๆและฉันแค่อยากให้แน่ใจว่าฉันไม่ได้พลาดอะไรไปที่อื่น
Michael Burr

2
สิ่งนี้ใช้ได้กับสิ่งที่เพิ่มขึ้นด้วยหรือไม่?
Ferruccio
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.