2015-04-06 22 views
5

Tôi có một lớp mẫu lưu trữ một dãy số và tôi muốn áp dụng các hàm hiện có (vô hướng) cho mọi phần tử. Ví dụ, nếu chúng ta giả sử lớp của mình là std :: vector, thì tôi muốn có thể gọi (ví dụ) hàm std :: cos trên tất cả các phần tử.Cách áp dụng hàm cho tất cả các phần tử trong mảng (trong lớp mẫu C++)

Có lẽ một cuộc gọi sẽ trông như thế này:

std::vector<float> A(3, 0.1f); 
std::vector<float> B = vector_function(std::cos, A); 

N.B. Tôi cũng phải xử lý std :: phức tạp <> loại (trong đó có phức tạp std :: cos chức năng được gọi là).

tôi thấy this answer đó đề nghị dùng các loại chức năng như một mẫu tham số:

template<typename T, typename F> 
std::vector<T> vector_function(F func, std::vector<T> x) 

Tuy nhiên, tôi không thể có được điều này để làm việc ở tất cả (có lẽ vì các chức năng như std :: tội lỗi và std: : cos là cả hai templated và quá tải?).

Tôi cũng đã thử sử dụng std::transform, nhưng điều này nhanh chóng trở nên rất xấu. Đối với các loại phi phức tạp, tôi quản lý để làm cho nó làm việc bằng cách sử dụng typedef:

std::vector<float> A(2, -1.23f); 
typedef float (*func_ptr)(float); 
std::transform(A.begin(), A.end(), A.begin(), (func_ptr) std::abs); 

Tuy nhiên, cố gắng lừa tương tự với std :: phức tạp <> loại gây ra một vụ tai nạn thời gian chạy.

Có cách nào tốt đẹp để làm việc này không? Tôi đã bị mắc kẹt về điều này cho các lứa tuổi.

+1

Làm thế nào về chỉ đơn giản là 'std :: for_each'? – PaulMcKenzie

+1

Để bắt đầu "xấu xí", nếu bạn đang sử dụng C++ 11, 'std :: transform' có thể được viết bằng lambda mà không có' typedef': http://ideone.com/stYywt – PaulMcKenzie

+0

Tôi tra cứu 'std :: for_each' và có vẻ như chỉ cần gọi một hàm trên mỗi giá trị ... Tôi không thể thấy làm thế nào để giữ kết quả (có thể 'std :: transform' là thích hợp hơn trong khía cạnh này?). Tôi chưa từng sử dụng hàm lambda trước đây. Có cách nào để làm cho chung/templated? – Harry

Trả lời

0

Bạn nên xem bài đăng này của Richel Bilderbeek (Math code snippet to make all elements in a container positive), trong đó cho bạn thấy lý do tại sao abs không hoạt động trong quá trình chuyển đổi như vậy. Lý do nó không hoạt động là do cấu trúc hàm abs (xem http://www.cplusplus.com/reference/cstdlib/abs/). Bạn sẽ thấy abs không phải là templated chính nó không giống như một số chức năng khác (chủ yếu là chức năng nhị phân) được tìm thấy trong thư viện functional. Một giải pháp có sẵn trên trang web của Richel để chỉ cho bạn cách bạn áp dụng abs để nói, một vectơ số nguyên.

Bây giờ nếu bạn muốn áp dụng abs vào một thùng chứa bằng cách biến đổi, bạn nên biết rằng hàm chuyển đổi nhận được một đối tượng phức tạp và sẽ không biết cách áp dụng abs cho nó. Cách dễ nhất để giải quyết điều này là chỉ cần viết các hàm unary templated của riêng bạn.

Tôi có ví dụ bên dưới nơi tôi áp dụng abs cho các phần thực và tưởng tượng của đối tượng phức tạp.

#include <iostream> 
#include <vector> 
#include <algorithm> 
#include <complex> 
#include <functional> 

template <typename T> 
std::complex<T> abs(const std::complex<T> &in) { 
    return std::complex<T>(std::abs(in.real()), std::abs(in.imag())); 
}; 

int main() { 
    std::vector<std::complex<int>> v; 
    std::complex<int> c(10,-6); 
    v.push_back(c); 
    std::complex<int> d(-5, 5); 
    v.push_back(d); 

    std::transform(v.begin(), v.end(), v.begin(), abs<int>); 

    //Print out result 
    for (auto it = v.begin(); it != v.end(); ++it) 
    std::cout << *it << std::endl; 

    return 0; 
} 

Như bạn đã đề cập, bạn muốn áp dụng abs từ thư viện complex. Để tránh hành vi không xác định (xem this), bạn sẽ sử dụng tên kiểu double cho các đối tượng phức tạp.

+0

Cảm ơn, tôi sẽ đọc qua điều đó. Nhưng 'abs()' không được định nghĩa theo cách này cho các số phức. Đối với một số phức z, abs (z) = sqrt (z.real() * z.real() + z.imag() * z.imag()). Trong trường hợp này, kiểu trả về sẽ không phức tạp. Việc thực hiện C++ được mô tả tại [link] (http://www.cplusplus.com/reference/complex/abs/). – Harry

+0

Vì vậy, điều này vẫn hoạt động nếu tôi thay đổi chức năng để std :: cos. Nó cũng hoạt động nếu tôi giữ chức năng như std :: abs và thay đổi kiểu đầu vào thành std :: vector . Tuy nhiên, nó không hoạt động nếu tôi thay đổi cả chức năng và loại. Tôi nhận được 'lỗi: không thể chuyển đổi 'std :: complex ' thành 'double' trong bài tập'. – Harry

+0

Tôi không hiểu, bạn thay đổi chức năng để std :: cos và đang sử dụng std :: vector >? Cái này làm việc tốt cho tôi. – Mohammad

4

tôi vẫn nghĩ rằng bạn nên sử dụng std::transform:

template <class OutputIter, class UnaryFunction> 
void apply_pointwise(OutputIter first, OutputIter last, UnaryFunction f) 
{ 
    std::transform(first, last, first, f); 
} 

chức năng này hoạt động không chỉ đối với std::vector loại nhưng thực sự bất kỳ container mà có một chức năng begin()end() thành viên, và nó thậm chí còn làm việc cho C- mảng kiểu với sự trợ giúp của các hàm miễn phí std::beginstd::end. Hàm unary có thể là bất kỳ hàm miễn phí nào, một đối tượng functor, một biểu thức lambda hoặc thậm chí các hàm thành viên của một lớp.

Đối với vấn đề với std::sin, chức năng miễn phí này được tạo khuôn mẫu và do đó trình biên dịch không thể biết bạn cần mẫu instantiation nào.

Nếu bạn có quyền truy cập vào C++ 11, sau đó chỉ cần sử dụng một biểu thức lambda:

std::vector<float> v; 
// ... 
apply_pointwise(v.begin(), v.end(), [](const float f) 
{ 
    return std::sin(f); 
}); 

Bằng cách này, trình biên dịch biết rằng nó nên thay T=float như các mẫu tham số.

Nếu bạn có thể sử dụng chức năng C, bạn cũng có thể sử dụng chức năng sinf, mà không templated và mất một float như một tham số:

apply_pointwise(v.begin(), v.end(), sinf); 
+0

Tôi vừa nâng cấp trình biên dịch của mình ngày hôm qua để có thể sử dụng C++ 11. Cú pháp lambda đó trông khá kỳ lạ, vì vậy nó sẽ đưa tôi một thời gian để tìm ra những gì đang xảy ra ... nhưng có vẻ như tôi phải viết kiểu mẫu 'v' theo cách thủ công. Điều gì sẽ xảy ra nếu loại 'v' thay đổi? Tôi có thể nhận được chức năng tự động tìm ra điều này không? – Harry

+0

@Harry 'Cú pháp lambda đó trông khá kỳ lạ' Làm quen với nó. Nó là một phần chính của C++ 11. Thứ hai, thay đổi kiểu 'v' thành các kiểu khác? Dù nó là gì, nó phải đáp ứng các điều kiện mà bạn đã thiết lập - bạn không thể giả định rằng bạn có thể viết một hàm nơi 'v' có thể là bất cứ điều gì bạn muốn nó được. – PaulMcKenzie

+0

Tôi nghĩ ý tôi là, nếu 'v' chỉ là một vô hướng, thì (bất kể loại của nó) tôi có thể truyền nó vào' std :: cos' và hàm thích hợp sẽ luôn được gọi tự động. Tôi đoán tôi có thể có thể đạt được cùng cho 'std :: vector' s bằng cách quá tải mỗi chức năng riêng lẻ, nhưng không sử dụng một chức năng chung ... – Harry

Các vấn đề liên quan