Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 427
plasmacel Avatar asked Sep 20 '15 01:09

plasmacel


People also ask

What is the use of Variadic templates?

With the variadic templates feature, you can define class or function templates that have any number (including zero) of parameters. To achieve this goal, this feature introduces a kind of parameter called parameter pack to represent a list of zero or more parameters for templates.

What is Variadic template in C++?

Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration.

What are Variadic functions in C?

Variadic functions are functions that can take a variable number of arguments. In C programming, a variadic function adds flexibility to the program. It takes one fixed argument and then any number of arguments can be passed.

What is args && args?

Args declares an "object parameter" (pass by value) and Args&& declares a reference parameter (pass by reference). Passing by reference allows one to avoid copying the argument when that is unnecessary.


2 Answers

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.

like image 114
R Sahu Avatar answered Oct 30 '22 12:10

R Sahu


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

like image 33
P0W Avatar answered Oct 30 '22 12:10

P0W