嵌套模板函数作为参数

Nested Template Functions as Parameters

本文关键字:参数 函数 嵌套      更新时间:2023-10-16

在Python中,有一种非常简单的方法来装饰函数,这样您就可以在函数之前和/或之后添加额外的功能。在最简单的形式中,它看起来是这样的:

from random import SystemRandom
from time import time
import functools
rdev = SystemRandom()
def time_function(func):
    @functools.wraps(func)
    def run(*args, **kwargs):
        start = time()
        ret = func(*args, **kwargs)
        print("Took {:0.5f}s".format(time()-start))
        return ret
    return run
@time_function
def foo():
    x = [rdev.randint(1, 1000) for _ in range(10000)]
    sorted(x)
foo()  # prints "Took 0.04239s"

我想用C++编写一些具有类似功能的东西。我想把一个带有任意参数和返回类型的函数传递到一个函数中,让它执行一些操作。这就是我想到的:

#ifndef TIMEIT_H
#define TIMEIT_H
#include <string>
#include <functional>
#include <iostream>
#if defined(_WIN32)
#include <Windows.h>
namespace timeit {
    unsigned long gettime(void) {
        return GetTickCount();
    }
}
#elif defined(__linux__)
namespace timeit{
    unsigned long gettime(void) {
        return 0; // implement later
    }
}
#endif
namespace timeit {
    template <typename> struct timer_s;
    template<typename... Args> // this is required for any void function
    struct timer_s<void(Args ...)> {
        std::function<void(Args ...)> func;
        timer_s(std::function<void(Args ...)> f) : func{ f } {}
        void operator()(unsigned long &time, Args ... args) {
            unsigned long start = gettime();
            func(args ...);
            time = gettime() - start;
            return;
        }
    };
    template <typename T, typename... Args> // this is for any other return type
    struct timer_s<T(Args ...)> {
        std::function<T(Args ...)> func;
        timer_s(std::function<T(Args ...)> f) : func{ f } { }
        T operator()(unsigned long &time, Args ... args) {
            unsigned long start = gettime();
            T ret = func(args ...);
            time = gettime() - start;
            return ret;
        }
    };
    template<typename T, typename... Args>
    timer_s<T(Args...)> timer(T(*func)(Args ...)) {
        return timer_s<T(Args ...)>(std::function<T(Args ...)>(func));
    }
}
#endif//TIMEIT_H

这相当有效。例如,我可以用以下内容对任何函数进行计时:

static std::random_device rdev;
unsigned int foo(size_t size){
    std::vector<unsigned int> nums(size);
    std::mt19937 rand(rdev());
    std::generate(nums.begin(), nums.end(), rand);
    std::sort(nums.begin(), nums.end());
    return nums.back(); // return largest number
}
int main(){
    //foo(0xffff); // normal call
    unsigned long foo_time = 0;
    auto t_foo = timeit::timer(foo);
    unsigned int largest = t_foo(foo_time, 0xffff); // stores time
    std::cout << "Took " << foo_time << "msnLargest number: " << largest << "n";
    return 0;
}

当我尝试直接为std::sort之类的模板化函数计时时,就会出现问题。只有指定了确切的类型,我才能执行此操作。我想我想知道C++是否能够进行嵌套模板推导。我想让它推断出我正在使用哪种形式的std::sort,并动态地更改t_sort的实现:

我目前正在做的事情:

static std::random_device rdev;
int main(){
    std::vector<unsigned int> nums(size);
    std::mt19937 rand(rdev());
    std::generate(nums.begin(), nums.end(), rand);
    auto t_sort = timeit::timer(std::sort<std::vector<unsigned int>::iterator>);
    unsigned long sort_time = 0;
    t_sort(sort_time, nums.begin(), nums.end());
}

我想要什么:

static std::random_device rdev;
int main(){
    std::vector<unsigned int> nums(size);
    std::mt19937 rand(rdev());
    std::generate(nums.begin(), nums.end(), rand);
    auto t_sort = timeit::timer(std::sort); // this line is different
    unsigned long sort_time = 0;
    t_sort(sort_time, nums.begin(), nums.end());
}

这可能吗?我最初的反应可能不是,但如果不是,为什么?

std::sort不是函数,而是函数模板,

一种解决方案是任何接受Functor:

namespace timeit {
    template <typename Functor>
    struct timer_s {
        Functor func;
        double time = 0;
        timer_s(Functor  f) : func{ f } {}
        template <typename ... Ts>
        auto operator()(Ts ... args) {
            struct Finally {
                ~Finally() {
                    time = std::chrono::duration_cast<std::chrono::milliseconds>
                              (std::chrono::system_clock::now() - start).count();
                }
                double& time;
                std::chrono::time_point<std::chrono::system_clock> start;
            } finally{time, std::chrono::system_clock::now()};
            return func(std::forward<Ts>(args)...);
        }
    };
    template<typename F>
    timer_s<F> timer(F f) {
        return timer_s<F>(f);
    }
}

然后称之为:

// Generic lambda to wrap std::sort and call the correct one.
auto t_sort = timeit::timer([](auto b, auto e){ return std::sort(b, e); });
std::vector<int> v {4, 8, 23, 42, 15, 16};
t_sort(v.begin(), v.end());

演示

您似乎已经找到了答案,即c++中不存在类似python所提供的东西,但我想解决您的问题"为什么?"在可变模板的上下文中。

您可能得到的错误消息是"无法确定重载函数"std::sort"的哪个实例",这是因为您实际上没有将特定的函数指针传递给timerSort只有在声明其类型时才成为一个特定的函数,作为一个非专用模板,它只是一个函数的概念。编译器将仅根据您使用的特定函数和参数来推导timerTArgs的类型,因此特定函数是必要的。您有一个很好的动态键入想法,可以将该规范延迟到稍后将参数实际传递给t_sort时。编译器需要知道在使用构造函数初始化它时应该使用什么专用计时器。

获得为sort推断的类型的一种方法可以是将所有内容传递给timer构造函数(将计时器的执行延迟到以后)。毫无疑问,sort的类型可以从以下内容中推断出来:

auto t_sort = timeit::timer(std::sort, nums.begin(), nums.end());

工厂看起来像

template<typename T, typename... Args>
timer_s<T(Args...)> timer(T(*func)(Args ...), Args...)
{
    return timer_s<T(Args ...)>(std::function<T(Args ...)>(func), Args...);
}

我们希望编译器查看计时器方法内部,并清楚地看到Args...的类型是std::vector<unsigned int>::iterator,这就是应该用于函数指针输入的内容,这意味着sort应该专门用于std::vector<unsigned int>::iterator。当编译器查看方法签名时,它将Args...参数与迭代器类型相关联,并发现函数需要采用相同的类型。这很管用!

template<typename ... Args> // this is required for any void function
struct timer_s<void(Args... )> {
    std::function<void(Args ...)> func;
    std::tuple<Args...> m_args;

    timer_s(std::function<void(Args ...)> f, Args... args)
        : func{ f }, m_args(std::forward<Args>(args)...)
    {
    }

    void operator()(unsigned long &time) {
        unsigned long start = gettime();

//func(std::get(m_args)…)//稍后将对此进行详细介绍time=gettime()-start;

        return;
    }
};

template<typename... Args>
timer_s<void(Args...)> timerJBS(void(*func)(Args ...), Args... args)
{
    return timer_s<void(Args ...)>(std::function<void(Args ...)>(func), std::forward<Args>(args)...);
}

然后你就可以完全使用

auto t_sort = timeit::timer(std::sort, nums.begin(), nums.end());

好吧,所以这是用MSVC(VS2015)编译的,并且实现了在不指定参数的情况下使用std::sort的目标。不过,有一个问题,那就是对func的评估(我对此进行了评论)。出于某种原因,我得到了error C3546: '...': there are no parameter packs available to expand,这似乎是由于早期版本的编译器中的一个错误造成的。一些消息来源似乎说它已经修复,另一些则说它只是在路线图上。我认为弄清楚这一点超出了问题的范围,本质上是关于将std::sort传递给一个工厂函数,该函数生成了一个懒惰的求值包装器。我希望现在还可以,如果你不知道如何绕过编译器错误,也许可以试着发布另一个问题。另外,也许可以尝试使用其他编译器,但我没有这么做。

最后一个想法是,可能还有另一种方法可以实现我所概述的,将参数绑定到模板中的函数,使sort显式专用化。不过,我认为这个模式或多或少会遵循我所写的内容,唯一的区别是在timer_s结构中。