ฟังก์ชัน get_from_variant ใน C ++

Oct 24 2020

นี่คือคำถามติดตามสำหรับฟังก์ชัน recursive_transform สำหรับประเภทต่างๆที่ซ้อนกัน Iterable ด้วยมาตรฐาน :: การดำเนินงานที่แตกต่างกันใน C ในฐานะที่เป็นคำตอบของ G. Sliepenกล่าวเหลือเพียงซ้ำเปลี่ยนการดำเนินงานrecursive_transform()อาจจะเป็นความคิดที่ดี ดังนั้นการนำrecursive_transformฟังก์ชันไปใช้งานจึงถูกเก็บไว้ในรูปแบบต่อไปนี้ ยิ่งไปกว่านั้นการประกาศล่วงหน้าได้ถูกลบออก

template<class T, class _Fn> requires is_iterable<T>
static inline T recursive_transform(const T input, _Fn func)
{
    T returnObject = input;

    std::transform(input.begin(), input.end(), returnObject.begin(), func);
    return returnObject;
}

template<class T, class _Fn> requires is_iterable<T> && is_element_iterable<T>
static inline T recursive_transform(const T input, _Fn func)
{
    T returnObject = input;
    std::transform(input.begin(), input.end(), returnObject.begin(),
        [func](const auto& element)
        {
            return recursive_transform(element, func);
        }
    );
    return returnObject;
}

อย่างไรก็ตามฉันยังคงต้องการจัดการโครงสร้างสารประกอบที่มีช่วงและstd::variantเช่นstd::vector<std::variant<double>>. ฟังก์ชั่นใหม่get_from_variantเกิดขึ้นในความคิดของฉันเพื่อมุ่งเน้นไปที่การดำเนินการกับสิ่งเหล่านี้

template<typename T_variant, typename T>
static inline auto get_from_variant(T_variant input_variant)
{
    T return_val;
    std::visit([&](auto&& arg)
        {
            return_val = static_cast<T>(arg);
            return arg;
        },
        input_variant);
    return return_val;
}

การทดสอบget_from_variantฟังก์ชันนี้:

int main()
{
    //  get_from_variant function test
    std::variant<double> testNumber = 1;
    std::cout << get_from_variant<decltype(testNumber), double>(testNumber);
    
    //  The usage of recursive_transform function and get_from_variant function
    std::variant<double> variant_number = 3.14;
    
    std::vector<decltype(variant_number)> testVector1;
    testVector1.push_back(variant_number);
    testVector1.push_back(variant_number);
    testVector1.push_back(variant_number);
    std::cout << get_from_variant<std::variant<double>, double>(recursive_transform(testVector1, [](auto x){ return get_from_variant<std::variant<double>, double>(x) + 1; }).at(0)) << std::endl;
    
    return 0;
}

ยินดีรับข้อเสนอแนะทั้งหมด

  • คำถามใดที่ต้องติดตาม?

    ฟังก์ชัน recursive_transform สำหรับประเภทต่างๆที่ซ้อนกันซ้ำได้ด้วย std :: variant Implementation ใน C ++

  • มีการเปลี่ยนแปลงอะไรในโค้ดตั้งแต่คำถามสุดท้าย

    เพื่อที่จะจัดการกับโครงสร้างสารประกอบที่มีช่วงและstd::variantเช่นstd::vector<std::variant<double>>ในทางที่ดี, ฟังก์ชั่นใหม่get_from_variantได้ถูกสร้างขึ้น

  • เหตุใดจึงมีการขอรีวิวใหม่

    ในความคิดของฉันไม่แน่ใจว่าการออกแบบฟังก์ชั่นget_from_variantนั้นดีหรือไม่? ไอเดียหรือการใช้งานดีหรือไม่? ยินดีต้อนรับทุกความคิดเห็น

คำตอบ

2 Quuxplusone Oct 24 2020 at 23:08

ฉันไม่ได้ติดตามกระทู้นี้มาตั้งแต่ต้นดังนั้นฉันจึงสับสนมากกว่าที่คุณคิดว่าผู้อ่านจะมาถึงจุดนี้ เป็นความคิดที่ดีสำหรับคุณที่จะให้ตัวอย่างที่รวบรวมได้อย่างสมบูรณ์ทุกครั้งแม้จะเป็นลิงก์ Godbolt ก็ตามหากคุณต้องการให้คำถามมุ่งเน้นไปที่โค้ดเพียงเล็กน้อย

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


std::variant<double> testNumber = 1;

สิ่งนี้ไม่ได้รวบรวมใน C ++ 20 มันใช้เพื่อ? ถ้าเป็นเช่นนั้นนั่นเป็นการแบ่ง API ที่ค่อนข้างใหญ่สำหรับ C ++ ... แต่ไม่ใช่ปัญหาของคุณ อย่างไรก็ตามให้เปลี่ยน1.0และคอมไพล์ใหม่


template<typename T_variant, typename T>
static inline auto

สูญเสียstatic inline. เทมเพลตอินไลน์ตามคำจำกัดความได้อย่างมีประสิทธิภาพและคุณไม่ต้องการให้เทมเพลตนี้เป็นแบบคงที่ - คุณไม่ต้องการบังคับให้หน่วยการแปลแต่ละหน่วยเก็บสำเนาเฉพาะของตัวเองไว้ (ในกรณีที่อินไลน์เนอร์ไม่ได้รับการปรับให้เหมาะสมที่สุด)

ฉันไม่ได้เป็นแฟนของGiraffe_case. ชื่อพารามิเตอร์เทมเพลตควรสั้นและCamelCase; Vที่นี่ผมขอแนะนำให้

ของคุณแลมบ์ดามีที่ไร้ประโยชน์std::visit return arg;อันที่จริงฟังก์ชันทั้งหมดนี้ควรมีลักษณะดังนี้

template<class V, class T>
auto get_from_variant(V input) {
    return std::visit([&](auto&& arg) {
        return static_cast<T>(arg);
    }, input);
}

เมื่อลบส่วนที่ถูกลบออกเรามีเซลล์สมองที่มีอิสระที่จะมุ่งเน้นไปที่ระดับถัดไปของการอวดรู้: คุณกำลังรับargการอ้างอิงการส่งต่อ ( auto&&) แต่คุณไม่ได้ส่งต่อไปยังไฟล์static_cast. บางทีเราควรจะใช้ตรงstatic_cast<T>(static_cast<decltype(arg)>(arg))นี้เพื่อที่ว่าถ้าargเป็นการอ้างอิง rvalue มันจะถูกย้ายไปอยู่ในคอนTสตรัคเตอร์ของ?

แต่เดี๋ยวก่อน; argจะไม่อ้างอิง rvalue เพราะเรากำลังไปที่ lvalue input! ดังนั้นบางทีเราไม่ควรคาดหวังว่าจะแก้ไขสิ่งที่argเราเยี่ยมชม - เราสามารถใช้มันconst auto& argได้ แต่ถ้าเราไม่ได้คาดหวังที่จะปรับเปลี่ยนinputบางทีมันควรจะนำชีวินใช่รอสักครู่เรามีเหตุผลที่ทำสำเนาของinputที่นี่? เพียงแค่ใช้การอ้างอิง const เพื่อเริ่มต้นด้วย!

template<class V, class T>
auto get_from_variant(const V& input) {
    return std::visit([](const auto& arg) {
        return static_cast<T>(arg);
    }, input);
}

ฉันทิ้ง[&]แลมด้าไปแล้วเพราะมันไม่ต้องการการจับใด ๆ

เราควรดูพารามิเตอร์เทมเพลตget_from_variantด้วย Vสามารถอนุมานได้และTไม่สามารถ; มันสมเหตุสมผลเสมอที่จะใส่พารามิเตอร์ที่ไม่สามารถหักล้างได้ก่อนเสมอ

template<class T, class V>
auto get_from_variant(const V& input) {
    return std::visit([](const auto& arg) {
        return static_cast<T>(arg);
    }, input);
}

ตอนนี้คนขับหลักของเรามีลักษณะเช่นนี้ :

std::variant<double> testNumber = 1.0;
std::cout << get_from_variant<double>(testNumber);
    
std::vector testVector1 = {
    std::variant<double>(3.14),
    std::variant<double>(3.14),
    std::variant<double>(3.14),
};
std::cout << get_from_variant<double>(
    recursive_transform(testVector1, [](const auto& x){
        return get_from_variant<double>(x) + 1;
    }).at(0)
) << std::endl;

ในขณะเดียวกันในrecursive_transformคุณจะมีการพิมพ์ผิด: เมื่อคุณมีความหมายconst T input คุณสามารถ grep โดยอัตโนมัติสำหรับการพิมพ์ผิดเหล่านี้และคุณควร!const T& input

  • อีกครั้งลบstatic inlineจากเทมเพลต

  • ชื่อ_Fnนี้สงวนไว้สำหรับการนำไปใช้งาน เพียงใช้F.

  • funcไม่จำเป็นต้องคัดลอกลงในแลมด้า คุณควรใช้[&]เป็นค่าเริ่มต้นสำหรับแลมบ์ดาทุกตัวที่คุณเขียน (เว้นแต่ว่าข้างต้นคุณสามารถหลีกเลี่ยงได้[])

  • ตามจริงแล้วเว้นแต่คุณจะบ้าคลั่งเกี่ยวกับการทำตามสำนวน STL เพียงแค่ส่งการติดต่อกลับFโดยการอ้างอิง const และหลีกเลี่ยงการคัดลอก มีสถานที่ใน C ++ สำหรับการเรียกกลับแบบ stateful และไม่แน่นอน แต่transformไม่ใช่ที่นั้น

  • กรณีพื้นฐานของคุณมีความซับซ้อนมากกว่าที่จำเป็น มาแก้ไขกัน

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

template<class T, class F>
T recursive_transform(const T& input, const F& f) {
    return f(input);
}

template<class T, class F> requires is_iterable<T>
T recursive_transform(const T& input, const F& f) {
    T returnObject = input;
    std::transform(input.begin(), input.end(), returnObject.begin(),
        [&](const auto& element) {
            return recursive_transform(element, f);
        }
    );
    return returnObject;
}

แล้วสำหรับฉันจริงๆแล้วดูเหมือนว่าการใช้std::transformที่นี่จะมากเกินไป: มันอ่านจากinput สองครั้งหนึ่งครั้งเพื่อทำสำเนาและอีกครั้งเพื่อทำการแปลง สมมติว่าเราแค่เปิดโค้ดแบบนี้?

template<class T, class F> requires is_iterable<T>
T recursive_transform(const T& input, const F& f) {
    T output = input;
    for (auto&& elt : output) {
        elt = recursive_transform(elt, f);
    }
    return output;
}

แน่นอนว่าเราสามารถใช้ C ++ 20 Ranges เพื่อทำสิ่งนี้ได้ :

template<class T, class F> requires is_iterable<T>
T recursive_transform(const T& input, const F& f) {
    auto transformed = input | std::views::transform([&](auto&& x) {
        return recursive_transform(x, f);
    });
    return T(transformed.begin(), transformed.end());
}

การคอมไพล์และสร้างโค้ดที่ใหญ่กว่านั้นช้ากว่า - แต่มันอาจเร็วกว่าในรันไทม์หากT::value_typeมีราคาแพงในการคัดลอกเนื่องจากเรากำลังกำจัดการกำหนดสำเนาT::value_type- เรากำลังสร้างโดยตรงในสถานที่