简体   繁体   中英

Variadic member function of template class

I'm facing an issue where I'm trying to create a variadic member function with paramater pack of a specific type.

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

It is expected to work like:

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

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

However I get the following errors:

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

What would be the proper way to achieve the expected functionality?

A parameter pack can be used only if function is a function template.

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

A template parameter pack is a template parameter that accepts zero or more template arguments (non-types, types, or templates). A function parameter pack is a function parameter that accepts zero or more function arguments.

A template with at least one parameter pack is called a variadic template.

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

Also, use of && in the above function makes sense only if it is a template parameter. When you use && on the argument without the type being a template parameter, you cannot use:

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

You may, however, use

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

Make your pick. Keep the argument type as is and use std::move(a) or change the function to use a simple reference

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

and use

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

Update

You can use a helper class to make sure that all the arguments are of the right type.

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

and use:

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

With that,

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

still works but

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

fails.

I don't know whether being that strict with the argument types is called for in your case. You have a way if you need to.

One idea is to use a std::initializer_list instead, which will force the same type (of course you can probably get around this with a variadic template and some clever use of std::is_same to enforce the same type for all params of the variadic template):

#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

Can do some SFINAE tricks like :

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

Then,

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

You should use an argument pack. Also, why do you try to pass a rvalue reference?

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

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

Edit: If you want to verify that all the arguments are of type T, you can declare a verifying struct:

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

And then, you can add a line like (typename verify_params<T,Args...>::val)0; to your function.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM