recursive_transform สำหรับ std :: vector ที่มีประเภท return ต่างๆ

Oct 25 2020

นี่คือคำถามติดตามสำหรับฟังก์ชัน recursive_transform สำหรับประเภทต่างๆที่ซ้อนกัน Iterable ด้วยมาตรฐาน :: การดำเนินงานที่แตกต่างกันใน C ++และฟังก์ชั่น get_from_variant ใน C ขอขอบคุณG. SliepenและQuuxplusoneให้คำแนะนำการตรวจสอบโดยละเอียด อย่างไรก็ตามเวอร์ชันของrecursive_transformฟังก์ชันที่มีอยู่จะถือว่าประเภทการส่งคืนจะเหมือนกับประเภทอินพุตเสมอ กล่าวอีกนัยหนึ่งมันทำงานได้ดีกับฟังก์ชันแลมบ์ดาเช่น[](double x)->double { return x + 1; }(ประเภทของทั้งอินพุตและเอาต์พุตคือdouble) หรือ[](int x)->int { return x + 1; }(ประเภทของทั้งอินพุตและเอาต์พุตคือint) ในขั้นตอนต่อไปฉันต้องการมุ่งเน้นไปที่กรณีที่ประเภทการส่งคืนแตกต่างจากประเภทอินพุต ตัวอย่างเช่น,[](int x)->std::string { return std::to_string(x); }. เพราะแหล่งที่มาประเภทการกลับมาของrecursive_transformที่ระบุไว้ในTก็ไม่สามารถจัดการกับกรณีที่ประเภทของการส่งออกแปรรูปจากฟังก์ชั่นแลมบ์ดาที่แตกต่างจากf Tประเภทการเปลี่ยนแปลง Let 's Tเข้ามาautoดังต่อไปนี้ นี้autoไวยากรณ์ที่ใช้ที่นี่ทำให้ประเภทอันเกิดการปรับตัว

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

อีกส่วนหนึ่งrecursive_transformคือโครงสร้างแบบวนซ้ำและมีความซับซ้อนมากกว่าด้านบน ระบุคอนเทนเนอร์ไว้std::vectorที่นี่ก่อน

template<class T, class F> requires is_iterable<T>
auto recursive_transform(const T& input, const F& f) {
    typedef typename std::iterator_traits<typename T::iterator>::value_type
        value_type;

    std::vector<decltype(recursive_transform(std::declval<value_type&&>(), f))> output(input.size());
    std::transform(input.begin(), input.end(), output.begin(),
        [f](auto& element)
        {
            return recursive_transform(element, f);
        }
    );
    return output;
}

recursive_transformการทดสอบการทำงานของแม่แบบดังกล่าวข้างต้น

std::vector<int> test_vector = {
    1, 2, 3
};
auto recursive_transform_result = recursive_transform(
    test_vector,
    [](int x)->std::string { return std::to_string(x); });                          //  For testing

std::cout << "string: " + recursive_transform_result.at(0) << std::endl;            //  recursive_transform_result.at(0) is a std::string

std::vector<decltype(test_vector)> test_vector2 = {
    test_vector, test_vector, test_vector
};

auto recursive_transform_result2 = recursive_transform(
    test_vector2,
    [](int x)->std::string { return std::to_string(x); });                          //  For testing

std::cout << "string: " + recursive_transform_result2.at(0).at(0) << std::endl;     // recursive_transform_result.at(0).at(0) is also a std::string

ลิงค์ Godbolt อยู่ที่นี่

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

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

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

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

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

    ในrecursive_transformฟังก์ชันเวอร์ชันก่อนหน้านี้จะทำงานได้ดีเมื่อประเภทการส่งคืนเหมือนกับประเภทอินพุต แนวคิดหลักในคำถามนี้คือการพยายามใช้เวอร์ชันเพิ่มเติมซึ่งประเภทการส่งคืนจะแตกต่างจากประเภทอินพุต

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

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

คำตอบ

3 G.Sliepen Oct 25 2020 at 23:24

ฉันกลัวว่าความรู้ของฉันเกี่ยวกับเทมเพลตใน C ++ จะถึงขีด จำกัด แต่ฉันจะพยายามตอบให้ดีที่สุดเท่าที่จะทำได้:

ใช้std::back_inserter()เติมเวกเตอร์

แทนที่จะสร้างเวกเตอร์ในขนาดที่กำหนดเพียงแค่ประกาศเวกเตอร์ว่าง แต่สำรองความจุให้เพียงพอแล้วใช้std::back_inserter()เพื่อเติม:

std::vector<decltype(recursive_transform(std::declval<value_type&&>(), f))> output;
output.reserve(input.size());

std::transform(input.begin(), input.end(), std::back_inserter(output),
    [f](auto& element)
    {
        return recursive_transform(element, f);
    }
);

อย่างไรก็ตามหากคุณไม่ได้ใช้std::vectorแต่คอนเทนเนอร์ประเภทอื่นreserve()และ / หรือstd::back_inserter()อาจไม่เหมาะสม

การกำหนดประเภทคอนเทนเนอร์

ดังนั้นความนึกคิดเราไม่ต้องการที่จะประกาศแต่ภาชนะด้านนอกของประเภทstd::vector Tคุณสามารถใช้พารามิเตอร์เทมเพลตเทมเพลตเพื่อแยกโครงสร้างประเภทเทมเพลต:

template<template<class> class Container, class ValueType, class Function>
requires is_iterable<Container<ValueType>>
auto recursive_transform(const Container<ValueType> &input, const Function &f)
{
    // You want to be able to write this:
    using TransformedValueType = decltype(recursive_transform(*input.begin(), f));
    Container<TransformedValueType> output;
    ...
}

น่าเสียดายที่ไม่ได้ผลอย่างน้อยก็ไม่ใช่กับเสียงดังเพราะstd::vectorจริงๆแล้วมีพารามิเตอร์เทมเพลตสองรายการและคอนเทนเนอร์อื่น ๆ อาจมีพารามิเตอร์เทมเพลตมากหรือน้อย ดังนั้นวิธีแก้ปัญหานั้นคือการประกาศValueTypeเป็นชุดพารามิเตอร์เทมเพลต:

template<template<class...> class Container, class Function, class Ts...>
requires is_iterable<Container<Ts...>>
auto recursive_transform(const Container<Ts...> &input, const Function &f)
{
    using TransformedValueType = decltype(recursive_transform(*input.begin(), f));
    Container<TransformedValueType> output;
    ...
}

แม้ว่าจะไม่ส่งต่อพารามิเตอร์เทมเพลตที่สองก็ตาม รวมทุกอย่างไว้ด้วยกัน:

template<typename T>
concept is_iterable = requires(T x)
{
    *std::begin(x);
    std::end(x);
};

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

template<template<class...> class Container, class Function, class... Ts>
requires is_iterable<Container<Ts...>>
auto recursive_transform(const Container<Ts...> &input, const Function &f)
{
    using TransformedValueType = decltype(recursive_transform(*input.begin(), f));
    Container<TransformedValueType> output;

    std::transform(std::begin(input), std::end(input), std::back_inserter(output),
        [&](auto &element)
        {
            return recursive_transform(element, f);
        }
    );

    return output;
}