Я играю с лямбдами 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 ++. Я получил:
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_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());
}
Как я полагаю, другие упоминали в ваших комментариях, проблемы, связанные с вашим кодом, являются проблемами на всю жизнь. Обратите внимание, что вы передаете второй параметр, 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