จะจำกัดประเภทแพ็คเกจพารามิเตอร์ใน c ++ 11 ได้อย่างไร และจะใช้เทมเพลตใน cpp ได้อย่างไร?

สำหรับคำถามแรก:

ฉันต้องการเขียนฟังก์ชันเพื่อเชื่อมสตริงเข้าด้วยกัน และฟังก์ชันสามารถรับได้หลายสตริง

#include <string>
#include <vector>
#include <type_traits>

template <class... Args, typename std::enable_if<std::is_same<typename std::decay<Args...>::type, std::string>::type>::type>
std::string foo(const std::string &first, const Args &... senconds) {
    std::string delimiter = "$$";
    std::string ret = first;
    std::vector<std::string> vec{senconds...};
    for (auto second = vec.rbegin(); second != vec.rend(); second++) {
        ret = delimiter + *second + delimiter + ret;
    }
    return ret;
}

แต่เมื่อฉันเรียกใช้มันเช่น:

std::string name = "x";
name = foo(name, "xxx");

คอมไพเลอร์จะเกิดข้อผิดพลาด:

error: no matching function for call to ‘foo(std::__cxx11::string&, const char [4])’

และจะมีข้อความว่า

note: couldn't deduce template parameter ‘<anonymous>’

ฉันคิดว่าฉันควรแก้ไขข้อจำกัดในเทมเพลต และฉันได้ลองวิธีการที่เกี่ยวข้องทั้งหมดใน type_traits แล้ว แต่ก็ไม่ได้ผลเลย

สำหรับคำถามที่สอง:

ฉันต้องการซ่อนการใช้งานฟังก์ชันบางอย่าง แต่สำหรับฟังก์ชันเทมเพลต ไม่สามารถใส่คำจำกัดความใน .hpp ได้ และใส่การใช้งานใน .cpp คอมไพเลอร์จะส่งข้อผิดพลาด undefined reference มีวิธีที่สง่างามในการแก้ปัญหานี้หรือไม่?

ขอบคุณ.


person Patrick    schedule 28.11.2019    source แหล่งที่มา
comment
สำหรับคำถามที่สอง โปรดดู stackoverflow.com/questions/495021/ (และอย่าท้อแท้กับชื่อคำถาม) แต่โปรดอย่าถามคำถามสองข้อแยกกันในคราวเดียว   -  person Max Langhof    schedule 28.11.2019
comment
นอกเหนือจาก: คุณไม่จำเป็นต้องมีทุกองค์ประกอบของ Args... ให้เป็น std::string เพียงบางสิ่งที่คุณสามารถยกตัวอย่าง std::string ด้วย   -  person Caleth    schedule 28.11.2019
comment
@MaxLanghof ฉันจะแยกคำถามสองข้อแยกกันในครั้งต่อไป   -  person Patrick    schedule 29.11.2019


คำตอบ (2)


มีบางอย่างที่จะแกะที่นี่

  • std::decay<Args...>::type ไม่สามารถทำงานได้ std::decay รับอาร์กิวเมนต์เทมเพลตเดียว แต่คุณพยายามขยายแพ็กที่นี่ ส่วนขยายจะต้องเกิดขึ้นใน is_same

  • คุณยังขาดวิธีรวมเพรดิเคต is_same ทั้งหมด คุณต้องการ and ทั้งหมดหรือ or ทั้งหมดหรือไม่ น่าจะเป็น and ใน C++17 นั้นทำได้ง่ายด้วย fold expression แต่สำหรับ C++11 เราต้องทำงานอีกสักหน่อย

  • ในที่สุดสิ่งที่คอมไพเลอร์บ่น: std::enable_if<bla>::type ประเมินเป็น void ถ้า bla คือ true นั่นหมายความว่าคุณกำลังคาดหวังอาร์กิวเมนต์เทมเพลตที่ไม่ใช่ประเภทอย่างเป็นทางการ และคอมไพลเลอร์บ่นเนื่องจากไม่สามารถอนุมานได้ว่าค่าประเภท void ใดที่ควรอนุมานได้ โดยปกติแล้วจะบรรเทาลงได้ด้วยการสร้างตัวชี้ไปที่มันแทนและตั้งค่าเริ่มต้นเป็น nullptr: std::enable_if<bla>::type* = nullptr

  • ปรากฏ (?) ว่าคุณคาดหวังว่า foo(someString, "stringLiteral"); จะทำงาน จะไม่เป็นเช่นนั้น เนื่องจากสตริงลิเทอรัลไม่ใช่ std::string บางทีคุณอาจต้องการภาคแสดงอื่น แต่สำหรับคำตอบนี้ ฉันจะยึดติดกับเงื่อนไขดั้งเดิม


รวบรวมทั้งหมดเข้าด้วยกัน:

  • ใน C ++ 17 คุณจะเขียน

    template <class... Args,
        std::enable_if_t<
                (std::is_same_v<std::decay_t<Args>, std::string> && ...)
            >* = nullptr
        >
    

    https://godbolt.org/z/84Dcmt

  • ใน C++11 เราใช้ตัวช่วย this และเพิ่ม typename และกลับเข้าไป ::type คำฟุ่มเฟือย:

    template <class... Args,
        typename std::enable_if<
            var_and<
                std::is_same<typename std::decay<Args>::type, std::string>::value...
                >::value
            >::type* = nullptr
        >
    

    https://godbolt.org/z/2eFyX7

person Max Langhof    schedule 28.11.2019
comment
ฉันได้ลองสาธิต c++11 แล้ว แต่หากมีการเรียกใช้ foo(..) เช่น foo(name, xxx) ก็ยังไม่พบการใช้งานเทมเพลตที่ถูกต้อง - person Patrick; 29.11.2019
comment
<source>:36:12: error: no matching function for call to 'foo' name = foo(name, stringRef, name, stringRef, "xxx"); ฉันได้แก้ไขโค้ดเช่นนั้นแล้ว - person Patrick; 29.11.2019
comment
และเมื่อฉันใช้โค้ดนี้กับโปรเจ็กต์ของฉัน มีข้อผิดพลาด: error: no type named ‘type’ in ‘struct std::enable_if<false, void>’ - person Patrick; 29.11.2019
comment
@Patrick อย่างที่ฉันพูดไปแล้วด้วย is_same คุณจะไม่สามารถส่งผ่านตัวอักษรสตริงได้ (ดูสัญลักษณ์แสดงหัวข้อย่อยสุดท้าย) และข้อผิดพลาดน่าจะเป็นเพราะคุณพยายามใช้มันอย่างไม่เหมาะสม แต่ฉันไม่สามารถวินิจฉัยได้โดยไม่เห็นสิ่งที่คุณทำ แต่ฉันเห็นว่าคุณคิดอะไรบางอย่างที่เหมาะกับคุณ นั่นเป็นสิ่งที่ดี - person Max Langhof; 29.11.2019

จากคำตอบของ MaxLanghof ฉันจึงเปลี่ยนเทมเพลตเป็น:

template <class... Args,
    typename std::enable_if<var_and<std::is_constructible<
        std::string, Args>::value...>::value>::type * = nullptr>

ในแบบฟอร์มนี้ คุณสามารถเรียกใช้ฟังก์ชัน foo ได้เหมือนกับ name = foo(name, stringRed, "xxx")

ขอบคุณ @MaxLanghof อีกครั้ง

person Patrick    schedule 29.11.2019