LINUX.ORG.RU

C++11 lambda c VariadicTemplate

 ,


0

5

Приветствую!

Есть такая задача - протащить некий функтор с переменным количеством аргументов в std::function<void()>. Так же нужно, чтоб сохранились ссылки там, где это нужно.

Изначально все было сделано примерно так

template <typename T, typename ...Args>
std::function<void()> createTask(T call, Args&& ... args)
{
    return std::bind(call, std::forward<decltype(args)>(args)...);
}

Естественно, если у меня есть вот такой вот код:

void inc(int &test, int i)
{
    test += i;
}
...
    int test = 0;
    auto t = createTask(inc, test, 1);
    t();

    std::cout << test << "\n";

то на выходе я получаю 0, ибо байнд теряет ссылку.

Выходом может стать std::ref, но оно не всегда очевидно.

Сделал вот такое вот решение.

template <typename T, typename ...Args>
std::function<void()> createTask(T call, Args&& ... args)
{
    return [call, &args...]() { call(std::forward<decltype(args)>(args)...); };
}

и оно, кажется, работает. А вопрос в том на сколько это корректный код вообще?

В стандарте 11 плюсов нашел вот такое

A capture followed by an ellipsis is a pack expansion (14.5.3). [Example:

template<class... Args>
void f(Args... args) {
    auto lm = [&, args...] { return g(args...); };
    lm();
}

— end example]

но можно ли захватить Args&& ... args по ссылке? И куда эта «ссылка» будет?

cast eao197



Последнее исправление: seryoga (всего исправлений: 1)

но можно ли захватить Args&& ... args по ссылке?

Это вряд ли, т.к. args в данном случае — это же не конкретный объект, ссылку на который можно получить, а всего лишь специальная синтаксическая конструкция.

Конкретно с вашим решением у вас может быть проблема вот в такой ситуации:

#include <functional>
#include <iostream>

template<typename Call, typename... Args>
std::function<void()> create_task(Call call, Args &&...args) {
	return { [call, &args...] { call(std::forward<Args>(args)...); } };
}

void f1(int & i, long & l, float & f) {
	++i;
	++l;
	f = 25.0;
}

void f2(int & i, long & l, float f) {
	++i;
	++l;
	std::cout << "f2: " << f << std::endl;
}

int main() {
	int i = 0;
	long l = 0;
	float f = 0.0;

	auto task1 = create_task(f1, i, l, f);
	auto task2 = create_task(f2, i, l, f);

	task1();
	task2();

	std::cout << i << ", " << l << ", " << f << std::endl;
}
При запуске вы получите:
f2: 25
2, 2, 25
Т.е. при создании task2 будет захвачена ссылка на переменную f, и в f2() будет передаваться текущее значение f (а не то значение f, которое было при вызове create_task(f2,...)). Что может стать (а может и не стать) проблемой. Зависит от того, что вам нужно.

Имхо, если вам нужно связывать функцию и аргументы-ссылки для нее, то лучше все-таки использовать std::ref при вызове create_task, код будет очевиднее и сюрпризов будет меньше.

#include <functional>
#include <iostream>

template<typename Call, typename... Args>
std::function<void()> create_task(Call call, Args &&...args) {
	return { [=]() mutable { call(std::forward<Args>(args)...); } };
}

void f1(int & i, long & l, float & f) {
	++i;
	++l;
	f = 25.0;
}

void f2(int & i, long & l, float f) {
	++i;
	++l;
	std::cout << "f2: " << f << std::endl;
}

int main() {
	int i = 0;
	long l = 0;
	float f = 0.0;

	auto task1 = create_task(f1, std::ref(i), std::ref(l), std::ref(f));
	auto task2 = create_task(f2, std::ref(i), std::ref(l), f);

	task1();
	task2();

	std::cout << i << ", " << l << ", " << f << std::endl;
}

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Т.е. при создании task2 будет захвачена ссылка на переменную f, ...

Ух ты! :) Чот вот об этом то и не подумал. Действительно совсем не очевидное поведение. Даже более чем.

тогда пусть будут ref. Спасибо.

seryoga
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.