2013-08-01 41 views
6

Tôi muốn tạo lớp mẫu có thể lưu trữ con trỏ hàm và đối số cho hàm này để hàm có thể được gọi sau đó với đối số này.Làm thế nào để lưu số lượng đối số biến bằng cách sử dụng các đối số mẫu của Denisdic?

Tôi muốn viết điều này một cách phổ biến và không phụ thuộc vào các loại đối số hoặc số.

Đây là một scatch của ý tưởng với việc sử dụng các mẫu variadic của C++ 11:

template<class T, typename... Params> 
class LazyEvaluation { 
private: 
    // Function to be invoked later 
    T (*f)(Params...); 
    // Params for function f 
    Params... storedParams; // This line is not compilable! 
    bool evaluated; 
    T result; 
public: 
    // Constructor remembers function pointer and parameters 
    LazyEvaluation(T (*f)(Params...),Params... params) 
    : f(f), 
    storedParams(params) //this line also cannot be compiled 
    {} 
    // Method which can be called later to evaluate stored function with stored arguments 
    operator T&() { 
      // if not evaluated then evaluate 
      if (! evaluated) { 
        result = f(storedParams...); 
        evaluated = true; 
      } 
      return result; 
    } 
} 

Tôi muốn có ít nhất giao diện của công chúng về an toàn kiểu lớp này nếu nó là có thể. Mặc dù nhận được công việc này ít nhất là bằng cách nào đó là quan trọng hơn.

Tôi đã quản lý để lưu số lượng đối số biến bằng cách nào đó. Nhưng tôi không thể chuyển chúng đến hàm f. Tôi sẽ viết nó cho câu trả lời, nhưng tôi muốn bạn suy nghĩ về các giải pháp của riêng bạn trước khi bạn thấy nỗ lực không hoạt động xấu xí của tôi.

Tôi đang tring để biên dịch mã ở trên với Microsoft Visual C++ Compiler tháng 11 năm 2012 CTP (v120_CTP_Nov2012), nhưng nó sẽ là tốt nhất nếu một giải pháp độc lập trình biên dịch sẽ tồn tại.

Cảm ơn bạn

+3

Tôi cho rằng đây là bản sao: http://stackoverflow.com/questions/14833129/a-clean-way-to-store-a-function-and-its-arbitrary-type-arbitrary-number-argum – hmjd

+4

Lưu trữ chúng trong một 'std :: tuple ' ... – kennytm

+0

@KennyTM Đó phải là câu trả lời. – Angew

Trả lời

0

Sau đây là cách tôi đã cố gắng để giải quyết nó:

Các gói parametr có thể recursivle mở rộng và mỗi parametr lưu. Cửa hàng chức năng là nghĩa vụ phải làm điều đó. Nó sử dụng chức năng trợ giúp một (hai lần quá tải).

template<typename T> 
void storeHelperFunction(void*& memory, T last) { 
    *((T*)memory) = last; 
    memory = (void*)((char*)memory + sizeof(T)); 
} 

template<typename T, typename... Params> 
void storeHelperFunction(void*& memory, T first, Params... rest) { 
    storeHelperFunction(memory, first); 
    storeHelperFunction(memory, rest...); 
} 

template<typename... Params> 
void store(void* memory, Params... args) { 
    // Copy of pointer to memory was done when passing it to this function 
    storeHelperFunction(memory, args...); 
} 

Cửa hàng chức năng sẽ đưa con trỏ đến bộ nhớ mà số lượng đối số được cho là cần lưu.

Con trỏ có thể trỏ đến một số bộ nhớ được phân bổ động hoặc beter tới cấu trúc có kích thước bằng sizeof...(Params). cấu trúc như vậy có chính xác kích thước bất kỳ desiared có thể được xây dựng bằng mẫu Lập trình meta:

template <int N> 
struct allocatorStruct { 
    char byte1; 
    allocatorStruct<N-1> next; 
}; 

template <> 
struct allocatorStruct<1> {}; 

Tôi không chắc chắn những gì standart nói hoặc làm thế nào các trình biên dịch khác so với cái microsoft biên dịch nó. Nhưng sử dụng trình biên dịch của tôi sizeof (allocatorStruct) bằng N cho bất kỳ N nào lớn hơn hoặc bằng 1.

Do đó allocatorStruct<sizeof...(Params)> có cùng kích thước với Params.

Cách khác để tạo thứ gì đó có cùng kích thước với Params là sử dụng loại char [sizeof...(Params)]. Điều này có nhược điểm là trình biên dịch chỉ truyền con trỏ tới mảng này khi bạn cố gắng chuyển mảng đó làm đối số. Đó là lý do tại sao tốt hơn nên sử dụng allocatorStruct<sizeof...(Params)>.

Và bây giờ ý tưởng chính:

Khi lưu chức năng chúng tôi có thể truyền đến: T (*)(allocatorStruct<sizeof...(Params)>). Khi lưu các đối số cho hàm, chúng ta có thể lưu chúng vào cấu trúc của loại allocatorStruct<sizeof...(Params)>.

Kích thước của đối số là như nhau. Mặc dù con trỏ hàm nằm về kiểu hàm mà hàm được trỏ tới sẽ nhận dữ liệu của nó một cách chính xác.

Ít nhất tôi hy vọng.Tùy thuộc vào quy ước gọi điện, tôi cho rằng các đối số đã qua có thể được sắp xếp lại hoặc sai vì sự khác biệt giữa các đối số tiết kiệm từ trái sang phải và từ phải sang trái. Nhưng không phải vậy. Sử dụng quy ước gọi __cdecl chỉ đối số đầu tiên đã được thông qua và đối số khác đã bị mất. Với các quy ước gọi điện khác, chương trình đã ngừng hoạt động.

Tôi không dành nhiều thời gian gỡ lỗi và tìm dữ liệu trong bộ nhớ (trên ngăn xếp). Có ít nhất là đúng cách để đi?

+0

Liên kết bộ nhớ có vấn đề. Bạn không thể biết rằng 'memory + sizeof (T)' là một căn chỉnh tốt cho phần tử tiếp theo trong danh sách đối số, – selalerer

0

Đơn giản chỉ cần sử dụng một biểu thức lambda

// Some function. 
int add(int a, int b) { 
    return a + b; 
} 

auto lazyFunc = [] { return add(1, 2); }; 

std::cout << lazyFunc() << std::endl; // Evaluate function and output result. 

Nếu bạn thực sự muốn tạo ra một lớp mà chỉ đánh giá các chức năng một lần (lười biếng), sử dụng các mẫu variadic, bạn có thể làm một cái gì đó giống như trong đoạn mã sau.

Tôi cũng đã tạo lớp như vậy mà bạn không phải tạo một cá thể mới mỗi lần các tham số thay đổi. Tôi sử dụng một số std::tuple để lưu trữ các đối số đã cho và so sánh với các đối số đã cho trước đó. Nếu các đối số khác nhau, thì hàm sẽ được đánh giá lại.

Chức năng được truyền xung quanh và được lưu trữ bằng cách sử dụng một wrapper std::function vì vậy tôi không phải làm việc với các con trỏ chức năng thô (yuck).

#include <iostream> 
#include <functional> 
#include <utility> 
#include <tuple> 

template <typename T> 
class LazyEvaluation {}; 

template <typename ReturnType, typename... Params> 
class LazyEvaluation<ReturnType(Params...)> { 
private: 
    std::function<ReturnType(Params...)> func_; 
    ReturnType result; 
    std::tuple<Params...> oldParams; // Contains the previous arguments. 
public: 
    explicit LazyEvaluation(std::function<ReturnType(Params...)> func) 
     : func_(std::move(func)) {} 
    template <typename... Args> 
    ReturnType operator() (Args&&... args) { 
     auto newParams = std::make_tuple(std::forward<Args>(args)...); 

     // Check if new arguments. 
     if (newParams != oldParams) { 
      result = func_(std::forward<Args>(args)...); 
      oldParams = newParams; 
      std::cout << "Function evaluated" << std::endl; 
     } 

     std::cout << "Returned result" << std::endl; 
     return result; 
    } 
}; 

int main() { 
    auto f = [] (int a, int b) { 
     return a + b; 
    }; 

    // Specify function type as template parameter. 
    // E.g. ReturnType(Param1Type, Param2Type, ..., ParamNType) 
    LazyEvaluation<int(int, int)> ld(f); 

    std::cout << ld(1, 2) << std::endl; 
    std::cout << ld(1, 2) << std::endl; 
    std::cout << ld(3, 4) << std::endl; 
} 

Output:

Function evaluated 
Returned result 
3 
Returned result 
3 
Function evaluated 
Returned result 
7 
0

Với máy móc thiết bị tiêu chuẩn cho hình thành gói index variadic:

template <std::size_t... I> struct index_sequence {}; 
template <std::size_t N, std::size_t... I> 
struct make_index_sequence : public make_index_sequence<N-1, N-1, I...> {}; 
template <std::size_t... I> 
struct make_index_sequence<0, I...> : public index_sequence<I...> {}; 

và gọi chức năng với các đối số tuple giải nén:

template <typename Function, typename... Types, std::size_t... I> 
auto apply_(Function&& f, const std::tuple<Types...>& t, index_sequence<I...>) 
    -> decltype(std::forward<Function>(f)(std::get<I>(t)...)) { 
    return std::forward<Function>(f)(std::get<I>(t)...); 
} 

template <typename Function, typename... Types> 
auto apply(Function&& f, const std::tuple<Types...>& t) 
    -> decltype(apply_(f, t, make_index_sequence<sizeof...(Types)>())) { 
    return apply_(f, t, make_index_sequence<sizeof...(Types)>()); 
} 

này là khá str aightforward:

template<typename Function, typename... Params> 
class LazyEvaluation { 
private: 
    typedef decltype(std::declval<Function>()(std::declval<Params>()...)) result_type; 
    // Function to be invoked later 
    Function f; 
    // Params for function f 
    std::tuple<Params...> storedParams; 
    mutable bool evaluated; 
    union { 
    std::aligned_storage<sizeof(result_type)> space; 
    mutable result_type result; 
    }; 

    // Method which can be called later to evaluate stored function with stored arguments 
    void evaluate() const { 
    // if not evaluated then evaluate 
    if (! evaluated) { 
     new (&result) result_type{apply(f, storedParams)}; 
     evaluated = true; 
    } 
    } 

public: 
    // Constructor remembers function pointer and parameters 
    LazyEvaluation(Function f, Params... params) 
    : f(std::move(f)), 
     storedParams(std::move(params)...), 
     evaluated(false) 
    {} 
    ~LazyEvaluation() { 
    if (evaluated) 
     result.~result_type(); 
    } 

    operator result_type&() { 
    evaluate(); 
    return result; 
    } 

    operator const result_type&() const { 
    evaluate(); 
    return result; 
    } 
}; 

template <typename Function, typename... Params> 
LazyEvaluation<Function, Params...> 
make_lazy(Function&& f, Params&&... params) { 
    return {std::forward<Function>(f), std::forward<Params>(params)...}; 
} 

Tôi đã sử dụng một sự kết hợp và sắp xếp new để lưu trữ các kết quả đánh giá để nó không cần phải là một loại mặc định-constructible, và một số mutable thủ đoạn để một const LazyEvaluator thể được chuyển đổi cũng như một cá thể không phải const.

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