2016-04-19 2 views
0

В 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 << "ms\nLargest number: " << largest << "\n"; 
    return 0; 
} 

Проблема возникает, когда я пытаюсь время шаблонной функции, такие как std::sort непосредственно. Я могу сделать это только в том случае, если я укажу точный тип. Я предположил, что мне интересно, способен ли C++ делать вложенные шаблонные вычеты. Я хочу, чтобы вывести какую форму станд :: сортировать Я использую и изменить реализацию 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()); 
} 

Возможно ли это? Моя первоначальная реакция, вероятно, нет, но если нет, то почему?

+0

Как насчет 'timer ([&]() {return std :: sort (nums.begin(), nums.end());});' синтаксис? – Jarod42

+0

Я не обязательно против, но я думаю, что это делает его немного менее питоническим. Я знаю, что это ужасный, ужасный способ взглянуть на него, но мне нравится простота обертывания функции. Я полагаю, что мой вопрос заключается в том, может ли быть дедукция при использовании функции t_sort, а не при передаче в std :: sort. – Goodies

+0

Однако похоже, что я нашел ответ на свой вопрос: http://www.open-std.org/jtc1/sc22/wg21/docs/wp/html/oct97/template.html#temp.arg.template – Goodies

ответ

1

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()); 

Demo

0

Кажется, что вы нашли свой ответ что что-то не существует в C++, как и то, что предоставляет python, но я хотел бы затронуть ваш вопрос «почему?». в контексте вариативных шаблонов.

сообщение об ошибке, вы, вероятно, получаете «не может определить, какой экземпляр перегруженной функции„станд :: рода“предназначен» и это потому, что вы на самом деле не передавая конкретный указатель на функцию timer. Sort становится конкретной функцией, когда вы объявляете ее тип, как неспециализированный шаблон, это просто идея функции. Компилятор будет выводить типы для T и Args в timer, основываясь только на конкретных функциях и аргументах, которые вам нужны, поэтому необходима определенная функция. У вас есть эта хорошая динамически типизирующаяся идея отложить эту спецификацию до тех пор, пока вы фактически не передадите аргументы 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) и целями использования зОго :: рода без указания параметров достигаются. Есть уловка, хотя и это оценка func (что я прокомментировал). По какой-то причине я получаю error C3546: '...': there are no parameter packs available to expand, который, похоже, был из-за ошибки в более ранней версии компилятора. Некоторые источники, похоже, говорят, что исправлено, что другие говорят, что это только на дорожной карте. Я думаю, что выяснение этого выходит за рамки вопроса, который по существу касался передачи std :: sort в заводскую функцию, которая создавала ленивую оценочную оболочку. Надеюсь, сейчас все в порядке, если вы не можете понять, как обойти ошибку компилятора, попробуйте отправить другой вопрос. Кроме того, возможно, попробуйте использовать некоторые другие компиляторы, я этого не делал.

Заключительная мысль, может быть другой способ сделать то, что я описал, привязывая параметры к функции внутри шаблона, делая sort явно специализированным. Я думаю, что шаблон будет более или менее следовать тому, что я написал, но с единственными отличиями, которые находятся в структуре timer_s.

Смежные вопросы