Подскажите, пожалуйста, что происходит в этом коде?
Знаю, что здесь такие вопросы не особо любят, но подскажите, пожалуйста. Я встретил одну реализацию таймера, где увидел вот такое чудо. В шаблонах не силён, но здесь не понял ВООБЩЕ ничего:
template <class callable, class... arguments>
typename std::enable_if<!std::is_arithmetic<callable>::value>::type setTimer(const std::string &tag,float delay, callable&& f, arguments&&... args)
{
std::function<void()> task(std::bind(callable(f), std::forward<arguments>(args)...));
Timers.emplace_back(std::make_shared<CTimer>(std::move(task),tag,delay));
};
Если быть точным, то я не могу понять что происходит в первой строке:
typename std::enable_if<!std::is_arithmetic<callable>::value>::type setTimer(const std::string &tag,float delay, callable&& f, arguments&&... args), судя по всему это шаблон функции, где тип возвращаемого значения: typename std::enable_if<!std::is_arithmetic<callable>::value>::type.
Теперь по порядку:
!std::is_arithmetic<callable>::value- если тип арифметический , то вернетtrue, но здесь инверсия, значит результат будет обратныйstd::enable_if- если условие правдиво, то тип будет тот, который указан вторым аргументом (в данном случае не указан - значитvoid), иначе члена вообще не будет существовать.
Таким образом, если тип callable - арифметический, то функции не существует? Или что?
И если можно, пару слов об этом:
std::function<void()> task(std::bind(callable(f), std::forward<arguments>(args)...));
Потому что я пока вообще не разобрался..
Ответы (1 шт):
Да, если callable - арифметический тип, то произойдет ошибка подстановки аргументов шаблона. Т.е. это не будет ошибкой компиляции, и компилятор попытается найти другую реализацию setTimer. Ошибка компиляции, произойдет, только если не подойдет ни одна из перегрузок этой функции. Зачем эта проверка понадобилась автору - знает только автор. Возможно, есть еще одна версия setTimer, в которой 3-м аргументом должен быть обязательно арифметический тип? Логично выглядела бы проверка, что callable можно вызвать как функцию, с аргументами arguments: std::is_invocable<callable, arguments...>.
std::bind - рудимент пришедший из времен до c++11. Он позволяет связать функтор (т.е. функцию или объект с operator()) и некоторый набор аргументов, получив другой функтор, в котором в operator() , будет иметь меньше аргументов. В C++11, для этих же целей обычно используются лямбда-выражения, которые более наглядны. Но move и forward говорят что это уже c++11.
Таким образом, выражение std::bind(f, args...) производит нечто, что имеет operator(), при вызове которого будет вызван код f(args...).
std::forward - это аналог move, применяемый для шаблонных типов, который позволяет передвигать аргументы, только если это позволено семантикой конкретных подставленных в шаблон типов. Почему автор написал callable(f) вместо std::forward<callable>(f) - знает только автор. Полагаю, это ошибка: объект f все время будет копироваться, даже если его можно перемещать. Хотя, авто явно знает о move и forward.
std::function<void()> - универсальный контейнер в который можно положить что угодно, что имеет совместимый operator(). Т.е. в данном случае, оператор без аргументов, и возвращающий void. Это т.н. стирание типа. Мы расплачиваемся лишним вызовом виртуальной функции, при каждом вызове std::function::operator(), но получаем возможность сделать функцию, принимающею этот объект не шаблонной, а с аргументом конкретного типа. Вероятно, конструктор CTimer принимает именно его.