ข้อมูลประเภทไหลย้อนกลับใน C ++ เมื่อใด


92

ฉันเพิ่งดู Stephan T. Lavavej พูดในCppCon 2018เรื่อง "Class Template Argument Deduction" ซึ่งในบางครั้งเขาก็พูดว่า:

ข้อมูลชนิด C ++ เกือบจะไม่ไหลย้อนกลับ ... ผมต้องพูดว่า "เกือบ" เพราะมีหนึ่งหรือสองกรณีอาจจะมากกว่า แต่น้อยมาก

แม้จะพยายามคิดว่ากรณีใดที่เขาอาจอ้างถึง แต่ฉันก็คิดอะไรไม่ออก ดังนั้นคำถาม:

ในกรณีใดบ้างที่มาตรฐาน C ++ 17 กำหนดให้ข้อมูลประเภทเผยแพร่ย้อนกลับ


รูปแบบที่ตรงกับความเชี่ยวชาญเฉพาะบางส่วนและการมอบหมายการทำลายล้าง
v.oddou

คำตอบ:


80

นี่คืออย่างน้อยหนึ่งกรณี:

struct foo {
  template<class T>
  operator T() const {
    std::cout << sizeof(T) << "\n";
    return {};
  }
};

ถ้าคุณทำfoo f; int x = f; double y = f;ข้อมูลประเภทจะไหล "ถอยหลัง" จะคิดออกว่าสิ่งที่อยู่ในToperator T

คุณสามารถใช้สิ่งนี้ในขั้นสูงได้:

template<class T>
struct tag_t {using type=T;};

template<class F>
struct deduce_return_t {
  F f;
  template<class T>
  operator T()&&{ return std::forward<F>(f)(tag_t<T>{}); }
};
template<class F>
deduce_return_t(F&&)->deduce_return_t<F>;

template<class...Args>
auto construct_from( Args&&... args ) {
  return deduce_return_t{ [&](auto ret){
    using R=typename decltype(ret)::type;
    return R{ std::forward<Args>(args)... };
  }};
}

ตอนนี้ฉันทำได้

std::vector<int> v = construct_from( 1, 2, 3 );

และใช้งานได้

แน่นอนทำไมไม่ทำ{1,2,3}? ดี{1,2,3}ไม่ได้เป็นการแสดงออก

std::vector<std::vector<int>> v;
v.emplace_back( construct_from(1,2,3) );

ซึ่งเป็นที่ยอมรับต้องมีบิตเล่นกลเพิ่มเติมได้ที่: ตัวอย่างสด (ฉันต้องทำการส่งคืนการอนุมานให้ทำการตรวจสอบ SFINAE ของ F จากนั้นทำให้ F เป็น SFINAE ที่เป็นมิตรและฉันต้องบล็อก std :: initializer_list ในตัวดำเนินการ deduce_return_t T. )


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

5
ผู้&&ผ่านการคัดเลือกในครั้งoperator T()นี้เป็นสัมผัสที่ยอดเยี่ยม ช่วยหลีกเลี่ยงการโต้ตอบที่ไม่ดีautoโดยก่อให้เกิดข้อผิดพลาดในการคอมไพล์หากautoใช้ที่นี่ในทางที่ผิด
Justin

1
มันน่าประทับใจมากคุณช่วยชี้ให้ฉันดูข้อมูลอ้างอิง / พูดคุยกับแนวคิดในตัวอย่างได้ไหม หรืออาจจะเป็นต้นฉบับก็ได้ :) ...
llllllllll

3
@lili ไอเดียไหน? ฉันนับ 5: การใช้โอเปอเรเตอร์ T เพื่ออนุมานประเภทผลตอบแทน? ใช้แท็กเพื่อส่งผ่านประเภทอนุมานไปยังแลมบ์ดาหรือไม่? การใช้ตัวดำเนินการแปลงเพื่อม้วนโครงสร้างวัตถุตำแหน่งของคุณเองหรือไม่? เชื่อมต่อทั้ง 4?
Yakk - Adam Nevraumont

1
@lili Tha ตัวอย่าง "วิธีที่ก้าวหน้ากว่า" ก็อย่างที่บอกมีเพียง 4 ไอเดียหรือมากกว่านั้นที่รวมเข้าด้วยกัน ฉันทำการติดกาวทันทีสำหรับโพสต์นี้ แต่แน่นอนว่าฉันได้เห็นหลายคู่หรือแม้แต่สามคู่ที่ใช้ร่วมกัน มันเป็นเทคนิคที่คลุมเครือพอสมควร (ตามที่ tootsie บ่น) แต่ไม่มีอะไรแปลกใหม่
Yakk - Adam Nevraumont

31

Stephan T. Lavavej อธิบายกรณีที่เขาพูดถึงในทวีต :

กรณีที่ฉันคิดคือที่ที่คุณสามารถใช้ที่อยู่ของฟังก์ชันที่โอเวอร์โหลด / เทมเพลตและหากมีการใช้เพื่อเริ่มต้นตัวแปรประเภทใดประเภทหนึ่งสิ่งนั้นจะทำให้ไม่ชัดเจนว่าคุณต้องการอะไร (มีรายการสิ่งที่ทำให้เกิดความสับสน)

เราสามารถดูตัวอย่างของสิ่งนี้ได้จากหน้า cppreference ในที่อยู่ของฟังก์ชันโอเวอร์โหลดฉันได้ยกเว้นบางส่วนด้านล่าง:

int f(int) { return 1; } 
int f(double) { return 2; }   

void g( int(&f1)(int), int(*f2)(double) ) {}

int main(){
    g(f, f); // selects int f(int) for the 1st argument
             // and int f(double) for the second

     auto foo = []() -> int (*)(int) {
        return f; // selects int f(int)
    }; 

    auto p = static_cast<int(*)(int)>(f); // selects int f(int)
}

Michael Park เพิ่ม :

ไม่ จำกัด เฉพาะการเริ่มต้นประเภทคอนกรีตเช่นกัน นอกจากนี้ยังสามารถอนุมานได้จากจำนวนอาร์กิวเมนต์

และให้ตัวอย่างสดนี้ :

void overload(int, int) {}
void overload(int, int, int) {}

template <typename T1, typename T2,
          typename A1, typename A2>
void f(void (*)(T1, T2), A1&&, A2&&) {}

template <typename T1, typename T2, typename T3,
          typename A1, typename A2, typename A3>
void f(void (*)(T1, T2, T3), A1&&, A2&&, A3&&) {}

int main () {
  f(&overload, 1, 2);
}

ซึ่งผมอธิบายรายละเอียดเล็ก ๆ น้อย ๆเพิ่มเติมได้ที่นี่


4
เราสามารถอธิบายสิ่งนี้ได้ว่า: กรณีที่ประเภทของนิพจน์ขึ้นอยู่กับบริบท?
MM

20

ฉันเชื่อในการหล่อแบบคงที่ของฟังก์ชันที่โอเวอร์โหลดการไหลจะไปในทิศทางตรงกันข้ามเช่นเดียวกับความละเอียดเกินปกติ ฉันเดาว่าหนึ่งในนั้นคือถอยหลัง


7
ฉันเชื่อว่าสิ่งนี้ถูกต้อง และเมื่อคุณส่งชื่อฟังก์ชันไปยังประเภทตัวชี้ฟังก์ชัน พิมพ์ข้อมูลไหลจากบริบทของนิพจน์ (ประเภทที่คุณกำหนดให้ / สร้าง / etc) ย้อนกลับไปยังชื่อของฟังก์ชันเพื่อกำหนดว่าจะเลือกโอเวอร์โหลดใด
Yakk - Adam Nevraumont
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.