Неожиданный результат при попытке составить карри лямбда с другой лямбдой
Я играю с С++ 11 lambdas и пытаюсь имитировать некоторую функцию из functional
module языка программирования D. Я действительно пытался реализовать curry
и compose
. Вот main
, который я пытаюсь получить:
int main()
{
auto add = [](int a, int b)
{
return a + b;
};
auto add5 = curry(add, 5);
auto composed = compose(add5, add);
// Expected result: 25
std::cout << composed(5, 15) << std::endl;
}
Проблема в том, что я не получаю тот же результат от g++ и clang++. Я получаю:
- 35 с g++ 4.8.1
- 25 с g++ 4.8.2
- 25 с g++ 4.9
- 32787 с clang++ 3.5 (соединительная линия, используемая с Coliru)
g++ 4.8.2 и 4.9 дают ожидаемый результат. Результаты, полученные из g++ 4.8.1 и clang 3.5, не зависят от значения, переданного в curry
. Сначала я подумал, что это может быть ошибка компилятора, но более вероятно, что у меня есть ошибка в моем коде.
Вот моя реализация curry
:
template<typename Function, typename First, std::size_t... Ind>
auto curry_impl(const Function& func, First&& first, indices<Ind...>)
-> std::function<
typename function_traits<Function>::result_type(
typename function_traits<Function>::template argument_type<Ind>...)>
{
return [&](typename function_traits<Function>::template argument_type<Ind>&&... args)
{
return func(
std::forward<First>(first),
std::forward<typename function_traits<Function>::template argument_type<Ind>>(args)...
);
};
}
template<typename Function, typename First,
typename Indices=indices_range<1, function_traits<Function>::arity>>
auto curry(Function&& func, First first)
-> decltype(curry_impl(std::forward<Function>(func), std::forward<First>(first), Indices()))
{
using FirstArg = typename function_traits<Function>::template argument_type<0>;
static_assert(std::is_convertible<First, FirstArg>::value,
"the value to be tied should be convertible to the type of the function first parameter");
return curry_impl(std::forward<Function>(func), std::forward<First>(first), Indices());
}
И вот моя реализация compose
(обратите внимание, что я написал только двоичный compose
, а D - переменный):
template<typename First, typename Second, std::size_t... Ind>
auto compose_impl(const First& first, const Second& second, indices<Ind...>)
-> std::function<
typename function_traits<First>::result_type(
typename function_traits<Second>::template argument_type<Ind>...)>
{
return [&](typename function_traits<Second>::template argument_type<Ind>&&... args)
{
return first(second(
std::forward<typename function_traits<Second>::template argument_type<Ind>>(args)...
));
};
}
template<typename First, typename Second,
typename Indices=make_indices<function_traits<Second>::arity>>
auto compose(First&& first, Second&& second)
-> decltype(compose_impl(std::forward<First>(first), std::forward<Second>(second), Indices()))
{
static_assert(function_traits<First>::arity == 1u,
"all the functions passed to compose, except the last one, must take exactly one parameter");
using Ret = typename function_traits<Second>::result_type;
using FirstArg = typename function_traits<First>::template argument_type<0>;
static_assert(std::is_convertible<Ret, FirstArg>::value,
"incompatible return types in compose");
return compose_impl(std::forward<First>(first), std::forward<Second>(second), Indices());
}
Класс function_trait
используется для получения arty, возвращаемого типа и типа аргументов лямбда. Этот код сильно зависит от трюка индексов. Поскольку я не использую С++ 14, я не использую std::index_sequence
, а более старую реализацию под названием indices
. indices_range<begin, end>
- последовательность индексов, соответствующая диапазону [begin, end)
. Вы можете найти реализацию этих вспомогательных метафайлов (а также curry
и compose
) в онлайн-версии кода, но они менее значимы в этой проблеме.
У меня есть ошибка в реализации curry
и/или compose
или плохие результаты (с g++ 4.8.1 и clang++ 3.5) из-за ошибок компилятора?
РЕДАКТИРОВАТЬ: Вы можете найти код, который не совсем читается. Итак, вот версии curry
и compose
, которые являются точно такими же, но используют шаблоны псевдонимов, чтобы уменьшить шаблон. Я также удалил static_assert
s; в то время как они могут быть полезной информацией, что слишком много текста для вопроса, и они не участвуют в этой проблеме.
template<typename Function, typename First, std::size_t... Ind>
auto curry_impl(const Function& func, First&& first, indices<Ind...>)
-> std::function<
result_type<Function>(
argument_type<Function, Ind>...)>
{
return [&](argument_type<Function, Ind>&&... args)
{
return func(
std::forward<First>(first),
std::forward<argument_type<Function, Ind>>(args)...
);
};
}
template<typename Function, typename First,
typename Indices=indices_range<1, function_traits<Function>::arity>>
auto curry(Function&& func, First first)
-> decltype(curry_impl(std::forward<Function>(func), std::forward<First>(first), Indices()))
{
return curry_impl(std::forward<Function>(func), std::forward<First>(first), Indices());
}
template<typename First, typename Second, std::size_t... Ind>
auto compose_impl(const First& first, const Second& second, indices<Ind...>)
-> std::function<
typename result_type<First>(
typename argument_type<Second, Ind>...)>
{
return [&](argument_type<Second, Ind>&&... args)
{
return first(second(
std::forward<argument_type<Second, Ind>>(args)...
));
};
}
template<typename First, typename Second,
typename Indices=make_indices<function_traits<Second>::arity>>
auto compose(First&& first, Second&& second)
-> decltype(compose_impl(std::forward<First>(first), std::forward<Second>(second), Indices()))
{
return compose_impl(std::forward<First>(first), std::forward<Second>(second), Indices());
}
Ответы
Ответ 1
Как я считаю, другие упоминали в ваших комментариях, проблемы, связанные с вашим кодом, - это проблемы, связанные с жизнью. Обратите внимание, что вы передаете второй параметр, 5
, в curry
как rvalue:
auto add5 = curry(add, 5);
Затем при вызове функции curry
вы создаете копию этой переменной в стеке как один из параметров:
auto curry(Function&& func, First first)
Затем в своем обращении к curry_impl
вы передадите ссылку на first
, которая не будет существовать после завершения вашего вызова на curry
. Поскольку создаваемая вами лямбда использует ссылку на переменную, которая больше не существует, вы получаете поведение undefined.
Чтобы устранить проблему, которую вы испытываете, просто измените прототип curry
, чтобы использовать универсальную ссылку на first
и убедитесь, что вы не передаете rvalues на curry
:
template<typename Function, typename First,
typename Indices=indices_range<1, function_traits<Function>::arity>>
auto curry(Function&& func, First&& first)
-> decltype(curry_impl(std::forward<Function>(func), std::forward<First>(first), Indices()))
{
using FirstArg = typename function_traits<Function>::template argument_type<0>;
static_assert(std::is_convertible<First, FirstArg>::value,
"the value to be tied should be convertible to the type of the function first parameter");
return curry_impl(std::forward<Function>(func), std::forward<First>(first), Indices());
}
Затем в основном:
int foo = 5;
auto add5 = curry(add, foo);
Конечно, ограничение на выражение lvalue - довольно большая проблема с интерфейсом, поэтому стоит упомянуть, что если вы планируете использовать это вне упражнений, было бы неплохо предоставить интерфейс, в котором могут быть rvalues б.
Затем снова я изменил бы его так, чтобы полученный функтор имел копии его компонентов как std::bind
. Я знаю, что был бы немного озадачен, если следующий код не сработал:
std::function<int(int)> foo()
{
std::function<int(int, int)> add = [](int a, int b)
{
return a + b;
};
return curry(add, 5);
}
Изменить: теперь я вижу, что некоторые версии gcc по-прежнему требуют, чтобы значения были записаны по значению в полученную lamba. GCC 4.9.0 20131229 - это сборка, в которой я тестировал ее, на которой отлично работает.
Изменить # 2: указать правильное использование за Xeo