2015-09-20 37 views
7

Sono di fronte a un problema in cui sto cercando di creare una funzione membro variadic con un pacchetto di parametri di un tipo specifico.Funzione membro variabile della classe template

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

Si prevede di lavorare come:

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

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

Tuttavia ottengo i seguenti errori:

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...); 
        ~~~~^ 

Quale sarebbe il modo corretto per ottenere la funzionalità previsto?

risposta

4

Un pacchetto di parametri può essere utilizzato solo se la funzione è un modello di funzione.

Da http://en.cppreference.com/w/cpp/language/parameter_pack:

Un parametro pacchetto modello è un parametro di template che accetta zero o più argomenti di template (non-tipi, tipi o modelli). Un pacchetto di parametri funzione è un parametro di funzione che accetta zero o più argomenti di funzione.

Un modello con almeno un pacchetto di parametri è denominato modello variadico.

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

Inoltre, l'uso di && nella funzione sopra senso solo se è un parametro di template. Quando si utilizza && sull'argomento senza il tipo di essere un parametro di template, non è possibile utilizzare:

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

Si può, tuttavia, utilizzare

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

Fai la tua scelta. Tenere il tipo di argomento, come è e utilizzare std::move(a) o modificare la funzione di utilizzare un semplice riferimento

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

e utilizzare

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

Aggiornamento

È possibile utilizzare una classe di supporto per assicurarsi che tutti gli argomenti sono del tipo giusto.

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

ed impiego:

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

Con questo,

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

funziona ancora ma

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

fallisce.

Non so se nel tuo caso sia necessario essere così severi con i tipi di argomento. Hai un modo se ne hai bisogno.

+0

Non esiste un modo standard per applicare restrizioni sul tipo di argomenti o per utilizzare un tipo specifico nel pacchetto parametri? Se la risposta è no, l'unica cosa che posso fare è un static_assert con std :: is_same . – plasmacel

+0

@plasmacel, vedere l'aggiornamento –

+0

Una risposta perfetta. – plasmacel

1

È necessario utilizzare un pacchetto di argomenti. Inoltre, perché provi a passare un riferimento di 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: Se si desidera verificare che tutti gli argomenti sono di tipo T, è possibile dichiarare una struct verifica:

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

E poi, è possibile aggiungere una riga come (typename verify_params<T,Args...>::val)0; alla funzione .

+0

Il tipo di args non può essere di qualsiasi tipo. Deve essere typename Functional :: result_type. Voglio passare un riferimento di inoltro universale per evitare copie non necessarie. – plasmacel

+0

Per il primo numero, dovresti verificare gli elementi nel pacchetto (fammi pensare). Per il secondo, usa 'std :: move', o usa un riferimento a constvalue. – asaelr

2

Un'idea è invece utilizzare uno std::initializer_list, che imporrà lo stesso tipo (ovviamente è possibile aggirare questo con un modello variadic e un uso intelligente di std::is_same per applicare lo stesso tipo per tutti i parametri del modello 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

Può fare alcuni trucchi SFINAE come:

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

Poi,

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

Problemi correlati