Một hàm get_from_variant trong C ++

Oct 24 2020

Đây là câu hỏi tiếp theo cho Một hàm recursive_transform cho nhiều kiểu lặp lồng nhau có thể lặp lại với std :: biến thể Triển khai trong C ++ . Như câu trả lời của G. Sliepen đã đề cập, chỉ để lại hoạt động biến đổi đệ quy recursive_transform()có thể là một ý tưởng tốt hơn. Kết quả là, việc triển khai recursive_transformchức năng được giữ ở dạng sau. Hơn nữa, các khai báo kỳ hạn đã bị loại bỏ.

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

Tuy nhiên, tôi vẫn muốn xử lý cấu trúc phức hợp với các phạm vi và std::variant, chẳng hạn như std::vector<std::variant<double>>. Một chức năng mới get_from_variantxuất hiện trong đầu tôi để tập trung vào các hoạt động với những thứ này.

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

Các thử nghiệm của get_from_variantchức năng này :

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

Tất cả các đề xuất đều được chào đón.

  • Câu hỏi nào là tiếp theo?

    Một hàm recursive_transform cho nhiều kiểu lặp lồng nhau có thể lặp lại với std :: biến thể Triển khai trong C ++

  • Những thay đổi nào đã được thực hiện trong mã kể từ câu hỏi trước?

    Để xử lý cấu trúc phức hợp với các phạm vi và std::variant, chẳng hạn như std::vector<std::variant<double>>theo cách tốt hơn, một hàm mới get_from_variantđã được tạo.

  • Tại sao một đánh giá mới lại được yêu cầu?

    Theo tôi, tôi không rõ thiết kế công năng get_from_variantcó tốt không? Ý tưởng hay cách sử dụng có tốt hay không? Mọi bình luận đều được chào đón.

Trả lời

2 Quuxplusone Oct 24 2020 at 23:08

Tôi đã không theo dõi chủ đề này ngay từ đầu, vì vậy tôi bối rối hơn bạn mong đợi của độc giả vào thời điểm này. Bạn nên cung cấp một ví dụ hoàn chỉnh có thể tổng hợp được mỗi lần - thậm chí chỉ là một liên kết Godbolt, nếu bạn muốn giữ trọng tâm của câu hỏi vào một số đoạn mã nhỏ.

Trên thực tế, tôi thích xem liên kết Godbolt hơn ( ngoài việc xem mã trong câu hỏi như bạn đã thực hiện chính xác), vì nó giúp tôi đỡ rắc rối khi tự dán mã của bạn vào Godbolt. :) Đây là một liên kết đến mã của bạn: Godbolt .


std::variant<double> testNumber = 1;

Điều này không biên dịch trong C ++ 20. Nó đã sử dụng để? Nếu đúng như vậy, đó là một lỗi API khá lớn cho C ++ ... nhưng không phải là vấn đề của bạn. Dù sao, hãy thay đổi nó thành 1.0và biên dịch lại.


template<typename T_variant, typename T>
static inline auto

Mất static inline. Các mẫu có hiệu quả nội dòng theo định nghĩa và bạn không muốn mẫu này tĩnh - bạn không muốn buộc mỗi đơn vị dịch phải giữ bản sao duy nhất của riêng nó (trong trường hợp nó không được tối ưu hóa bởi bộ nội tuyến).

Tôi không phải là một fan hâm mộ của Giraffe_case. Tên tham số mẫu phải ngắn và CamelCase; ở đây tôi giới thiệu V.

std::visitLambda của bạn có một vô dụng return arg;. Trên thực tế, toàn bộ chức năng này sẽ trông giống như

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

Với việc loại bỏ điểm mấu chốt, chúng ta có các tế bào não tự do tập trung vào cấp độ tiếp theo: Bạn đang thực hiện argbằng cách chuyển tiếp tham chiếu ( auto&&), nhưng bạn không thực sự chuyển tiếp nó tới static_cast. Có lẽ chúng ta nên sử dụng static_cast<T>(static_cast<decltype(arg)>(arg))ở đây, để nếu arglà một tham chiếu rvalue, nó sẽ được chuyển vào hàm tạo Tcủa?

Nhưng chờ đợi; argsẽ không bao giờ là một tham chiếu rvalue, bởi vì chúng tôi đang truy cập một lvalue input! Vì vậy, có lẽ chúng ta không nên mong đợi sửa đổi lượt argtruy cập - chúng ta có thể coi đó là const auto& arg. Nhưng nếu chúng tôi không mong đợi sửa đổi input, có lẽ nó nên được thực hiện bằng cách— ừ, chờ một chút, tại sao chúng tôi lại tạo một bản sao của nó inputở đây? Chỉ cần lấy nó bằng tham chiếu const để bắt đầu!

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

Tôi đã bỏ [&]từ lambda, vì nó không yêu cầu bất kỳ ảnh chụp nào.

Chúng ta cũng nên xem xét các thông số mẫu get_from_variant. Vcó thể được suy luận và Tkhông thể; luôn luôn có ý nghĩa khi đặt các tham số không thể suy diễn lên hàng đầu.

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

Bây giờ trình điều khiển chính của chúng tôi trông như thế này :

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;

Trong khi đó, recursive_transformbạn có lỗi đánh máy: const T inputkhi bạn có ý const T& input. Bạn có thể sửa lỗi chính tả một cách máy móc, và bạn nên làm như vậy!

  • Một lần nữa, xóa static inlinekhỏi các mẫu.

  • Tên _Fnđược dành riêng cho việc thực hiện; chỉ sử dụng F.

  • Sao chép funcvào lambda là không cần thiết; bạn nên sử dụng [&]làm mặc định cho mỗi lambda bạn viết (trừ khi, như trên, bạn có thể loại bỏ đơn giản []).

  • Thành thực mà nói, trừ khi bạn là dại về sau thành ngữ STL, chỉ cần vượt qua gọi lại Fbằng cách tham chiếu const và tránh bao giờ sao chép nó. Có một vị trí trong C ++ cho các lệnh gọi lại trạng thái, có thể thay đổi, nhưng transformkhông phải là nơi đó.

  • Trường hợp cơ sở của bạn phức tạp hơn mức cần thiết. Hãy khắc phục điều đó.

Để tất cả chúng cùng nhau:

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

Và sau đó, tôi thực sự thấy rằng việc sử dụng std::transformở đây là quá mức cần thiết: nó đọc từ input hai lần , một lần để tạo bản sao và một lần nữa để thực hiện biến đổi. Giả sử chúng ta chỉ mở mã hóa nó, như thế này?

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

Tất nhiên, chúng ta có thể sử dụng C ++ 20 Ranges để làm điều gì đó như sau :

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

Điều đó chậm hơn để biên dịch và tạo mã lớn hơn - nhưng nó thực sự có thể nhanh hơn trong thời gian chạy, nếu T::value_typetốn kém để sao chép, bởi vì chúng tôi đang loại bỏ các nhiệm vụ sao chép T::value_type- chúng tôi chỉ đang xây dựng trực tiếp tại chỗ.