模板类的可变成员函数

Variadic member function of template class

本文关键字:成员 函数      更新时间:2023-10-16

我正面临一个问题,我正试图创建一个具有特定类型参数包的可变成员函数。

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

预期的工作方式如下:

A<int> a;
B<A<int>> b;
int result = b(a, 2, 3); // should return 5

但是我得到以下错误:

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

实现预期功能的正确方法是什么?

参数包只能在function是函数模板时使用。

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

:

模板形参包是一个接受零个或多个模板实参(非类型、类型或模板)的模板形参。函数形参包是接受零个或多个函数实参的函数形参。

至少有一个形参包的模板称为可变形参模板。

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

同样,在上面的函数中使用&&只有在它是模板形参时才有意义。当在实参上使用&&而类型不是模板形参时,不能使用:

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

但是,您可以使用

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

自己选吧。保持参数类型不变,使用std::move(a)或将函数更改为使用简单引用

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

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

你可以使用一个辅助类来确保所有的参数都是正确的类型。

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

和使用:

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

,

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

仍然有效但是

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

失败。

我不知道在你的情况下是否需要严格的参数类型。如果你需要,你有办法的。

一个想法是使用std::initializer_list代替,它将强制使用相同的类型(当然,您可能可以通过可变变量模板和一些聪明的使用std::is_same来解决这个问题,以强制可变变量模板的所有参数使用相同的类型):

#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

可以做一些SFINAE的技巧,比如:

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

,

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

您应该使用参数包。另外,为什么要传递一个右值引用呢?

template <typename Functor>
struct B
{
    using T = typename Functor::result_type;
    template<typename ...Args>
    T operator()(Functor f, Args... args)
    {
        return f(args...);
    }
};
编辑:如果你想验证所有的参数都是T类型,你可以声明一个验证结构体:
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;
};

然后,你可以添加像(typename verify_params<T,Args...>::val)0;这样的行到你的函数。