Подскажите, пожалуйста, что происходит в этом коде?

Знаю, что здесь такие вопросы не особо любят, но подскажите, пожалуйста. Я встретил одну реализацию таймера, где увидел вот такое чудо. В шаблонах не силён, но здесь не понял ВООБЩЕ ничего:

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.

Теперь по порядку:

  1. !std::is_arithmetic<callable>::value - если тип арифметический , то вернет true, но здесь инверсия, значит результат будет обратный
  2. std::enable_if - если условие правдиво, то тип будет тот, который указан вторым аргументом (в данном случае не указан - значит void), иначе члена вообще не будет существовать.

Таким образом, если тип callable - арифметический, то функции не существует? Или что?

И если можно, пару слов об этом: std::function<void()> task(std::bind(callable(f), std::forward<arguments>(args)...));

Потому что я пока вообще не разобрался..


Ответы (1 шт):

Автор решения: Chorkov

Да, если 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 принимает именно его.

→ Ссылка