Неожиданный результат при попытке составить лямбду с карри с другой лямбда

Я играю с лямбдами C ++ 11 и пытаюсь имитировать какую-то функцию из functional модуль языка программирования 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 с г ++ 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's 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 используется для получения арности, типа возвращаемого значения и типа аргументов лямбды. Этот код сильно зависит от трюка с индексами. Так как я не использую C ++ 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_asserts; хотя они могут быть полезной информацией, это слишком много текста для вопроса, и они не играют роли в рассматриваемой проблеме.

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

12

Решение

Как я полагаю, другие упоминали в ваших комментариях, проблемы, связанные с вашим кодом, являются проблемами на всю жизнь. Обратите внимание, что вы передаете второй параметр, 5, чтобы curry в качестве значения:

auto add5 = curry(add, 5);

Затем в вызове curry функция, вы создаете копию этой переменной в стеке в качестве одного из параметров:

auto curry(Function&& func, First first)

Затем в вашем звонке curry_impl Вы передаете ссылку на first это не будет существовать после вашего звонка curry завершается. Поскольку лямбда, которую вы создаете, использует ссылку на переменную, которой больше не существует, вы получаете неопределенное поведение.

Чтобы решить проблему, которую вы испытываете, просто измените прототип curry использовать универсальную ссылку на first и убедитесь, что вы не передаете значения 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's first parameter");
return curry_impl(std::forward<Function>(func), std::forward<First>(first), Indices());
}

Тогда в основном:

int foo = 5;
auto add5 = curry(add, foo);

Конечно, ограничить себя выражениями lvalue — довольно большая проблема с интерфейсом, поэтому стоит отметить, что если вы планируете использовать это вне упражнения, было бы неплохо предоставить интерфейс, в котором можно использовать значения rvalue.

С другой стороны, я бы изменил его так, чтобы получившийся функтор имел копии своих компонентов как 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 по-прежнему требуют, чтобы значения были записаны по значению в результирующую лямбу. GCC 4.9.0 20131229 — это тестовая сборка, которая работает нормально.

Правка № 2: указано правильное использование в соответствии с Xeo

3

Другие решения


По вопросам рекламы [email protected]