2015-09-20 22 views
7

Tôi đang gặp phải sự cố khi tôi đang cố tạo hàm thành viên variadic với gói tham số của một loại cụ thể.Chức năng thành viên biến thể của lớp mẫu

template <typename T> 
struct A 
{ 
    using result_type = T; 

    T operator()(T a, T b) 
    { 
     return a+b; 
    } 
}; 

template <typename Functor> 
struct B 
{ 
    using T = typename Functor::result_type; 

    T operator()(Functor &&f, T... args) 
    { 
     return f(args...); 
    } 
}; 

Nó được dự kiến ​​sẽ hoạt động như:

A<int> a; 
B<A<int>> b; 

int result = b(a, 2, 3); // should return 5 

Tuy nhiên tôi nhận được các lỗi sau đây:

error: type 'T' (aka 'typename Functor::result_type') of function parameter pack does not contain any unexpanded parameter packs 
     T operator()(Functor &&f, T... args) 
            ~^~~~~~~~ 

error: pack expansion does not contain any unexpanded parameter packs 
      return f(args...); 
        ~~~~^ 

Điều gì sẽ là cách thích hợp để đạt được các chức năng dự kiến?

Trả lời

4

Chỉ có thể sử dụng gói tham số nếu chức năng là mẫu chức năng.

Từ http://en.cppreference.com/w/cpp/language/parameter_pack:

Một số mẫu gói là một tham số mẫu mà chấp nhận không hay nhiều mẫu đối số (không loại, chủng loại, hoặc các mẫu). Một gói tham số hàm là một tham số hàm chấp nhận các đối số hàm không hoặc nhiều hơn.

Mẫu có ít nhất một gói tham số được gọi là mẫu variadic.

template <typename ... Args> 
T operator()(Functor&& f, Args... args) 
{ 
    return f(args...); 
} 

Ngoài ra, sử dụng && trong hàm trên có ý nghĩa chỉ khi nó là một tham số mẫu. Khi bạn sử dụng && trên lý luận mà không loại là một tham số mẫu, bạn không thể sử dụng:

A<int> a; 
B<A<int>> b; 
int r = b(a, 2, 3); 

Bạn có thể, tuy nhiên, sử dụng

int r = b(std::move(a), 2, 3); 

Hãy lựa chọn của bạn. Giữ kiểu lập luận như là và sử dụng std::move(a) hoặc thay đổi chức năng sử dụng một tài liệu tham khảo đơn giản

template <typename ... Args> 
T operator()(Functor& f, Args... args) 
{ 
    return f(args...); 
} 

và sử dụng

int r = b(a, 2, 3); 

Cập nhật

Bạn có thể sử dụng một lớp helper để đảm bảo tất cả các đối số đều thuộc loại phù hợp.

template<typename ... Args> struct IsSame : public std::false_type {}; 

template<typename T> struct IsSame<T> : public std::true_type {}; 

template<typename T, typename ... Args> struct IsSame<T, T, Args...> : public std::true_type 
{ 
    static const bool value = IsSame<T, Args ...>::value; 
}; 

và sử dụng:

template <typename ... Args> 
T operator()(Functor&& f, Args... args) 
{ 
    static_assert(IsSame<T, Args...>::value, "Invalid argument type"); 
    return f(args...); 
} 

Cùng với đó,

A<int> a; 
B<A<int>> b; 
int r = b(std::move(a), 2, 3); 

vẫn hoạt động nhưng

r = b(std::move(a), 2, 3.0); 

thất bại.

Tôi không biết liệu việc đó có nghiêm ngặt với các loại đối số được gọi trong trường hợp của bạn hay không. Bạn có một cách nếu bạn cần.

+0

Không có cách nào tiêu chuẩn để áp dụng các hạn chế đối với loại arg, hoặc sử dụng một loại cụ thể trong gói tham số? Nếu câu trả lời là không thì điều duy nhất tôi có thể làm là một static_assert với std :: is_same . – plasmacel

+0

@plasmacel, xem cập nhật –

+0

Câu trả lời hoàn hảo. – plasmacel

1

Bạn nên sử dụng gói đối số. Ngoài ra, tại sao bạn cố gắng vượt qua một tham chiếu rvalue?

template <typename Functor> 
struct B 
{ 
    using T = typename Functor::result_type; 

    template<typename ...Args> 
    T operator()(Functor f, Args... args) 
    { 
     return f(args...); 
    } 
}; 

Edit: Nếu bạn muốn xác minh rằng tất cả các đối số của loại T, bạn có thể khai báo một struct xác minh:

template <typename T, typename ...Pack> 
struct verify_params {}; 

template <typename T> 
struct verify_params<T> { 
    using val=void; 
}; 

template <typename T, typename ...Pack> 
struct verify_params<T,T,Pack...> { 
    using val=typename verify_params<T,Pack...>::val; 
}; 

Và sau đó, bạn có thể thêm một dòng như (typename verify_params<T,Args...>::val)0; đến chức năng của bạn .

+0

Loại args không thể được bất kỳ loại. Nó phải là tên tập tin Functor :: result_type. Tôi muốn chuyển một tham chiếu chuyển tiếp phổ quát để tránh các bản sao không cần thiết. – plasmacel

+0

Đối với vấn đề đầu tiên, bạn nên xác minh các yếu tố trong gói (hãy để tôi nghĩ về nó). Đối với lần thứ hai, hãy sử dụng 'std :: move' hoặc sử dụng tham chiếu const lvalue. – asaelr

2

Một ý tưởng là sử dụng std::initializer_list thay vào đó, sẽ buộc cùng loại (tất nhiên bạn có thể nhận được thông tin này với mẫu variadic và sử dụng thông minh std::is_same để thực thi cùng loại cho tất cả thông số của mẫu variadic):

#include <algorithm> 
#include <initializer_list> 
#include <utility> 
#include <iostream> 

template <typename T> 
struct A 
{ 
    using result_type = T; 

    T operator()(std::initializer_list<result_type> const& li) 
    { 
     return std::accumulate(std::begin(li), std::end(li), 0.); 
    } 
}; 

template <typename Functor> 
struct B 
{ 
    using T = typename Functor::result_type; 

    T operator()(Functor &&f, std::initializer_list<T> args) 
    { 
     return f(args); 
    } 
}; 

int main() 
{ 
    A<int> functor; 
    B<decltype(functor)> test; 
    std::cout << test(std::move(functor), {1, 2, 3}); // displays 6 
} 

Live on Coliru

2

có thể làm một số thủ thuật SFINAE như:

struct Foo {}; 
template<class T, class...> 
struct all_same : std::true_type 
{}; 

template<class T, class U, class... SS> 
struct all_same<T, U, SS...> 
    : std::integral_constant<bool, std::is_same<T,U>{} && all_same<T, SS...>{}> 
{}; 

Sau đó,

template <typename Functor> 
struct B 
{ 
    using T = typename Functor::result_type; 

    template<typename ...Args> 
    T operator()(Functor&& f, Args... args) 
    { 
     static_assert(all_same<T, Args...>{}, "all not same types"); 
     return f(args...); 
    } 
}; 

Demo Here

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