I am toying with C++11 lambdas and was trying to mimick some function from the functional
module of the D programming language. I was actually trying to implement curry
and compose
. Here is the main
that I am trying to get working:
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;
}
The problem is that I don't get the same result from g++ and clang++. I get:
- 35 with g++ 4.8.1
- 25 with g++ 4.8.2
- 25 with g++ 4.9
- 32787 with clang++ 3.5 (trunk used with Coliru)
g++ 4.8.2 and 4.9 give me the expected result. The results obtained from g++ 4.8.1 and clang 3.5 do not depend on the value passed to curry
. I first thought that this may be a compiler bug, but it is more likely that I have an error in my code.
Here is my implementation of 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());
}
And here is my implementation of compose
(note that I only wrote a binary compose
while the D one is variadic):
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());
}
The class function_trait
is used to get the arity, the return type and the type of the arguments of a lambda. This code heavily relies on the indices trick. Since I don't use C++14, I don't use std::index_sequence
but an older implementation under the name indices
. indices_range<begin, end>
is an indices sequence corresponding to the range [begin, end)
. You can find the implementation of these helper metafunctions (as well as curry
and compose
) on the online version of the code, but they are less meaningful in this problem.
Do I have a bug in the implementation of curry
and/or compose
or are the bad results (with g++ 4.8.1 and clang++ 3.5) due to compiler bugs?
EDIT: You may find the code above not quite readable. So, here are versions of curry
and compose
that are exactly the same, but use alias templates to reduce the boilerplate. I also removed the static_assert
s; while they may be helpful information, that's just too much text for the question and they do not play a part in the problem at hand.
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());
}
std::bind
for the currying part? Likeauto add5 = std::bind(add, _1, 5);
? – Williemaewilliesfunction_traits
does not work for bind expressions; the reason would be that a bind expression has several overloads ofoperator()
. – Helicoreturn [&]
That looks highly suspicious. Such lambdas can be implemented by storing a pointer to EBP, instead of a reference to each captured entity. Are you sure you don't get lifetime issues with that? – Jerrodjerrol[&]
with a[=]
and the forwarding with moving, clang++ also prints 25. – Jerrodjerrol[&]
is actually the reason I asked the question. If there is problem, it has to be with lifetime. – Helicoauto curry(Function&& func, First first)
is takingfirst
by value, and then tries tostd::forward
it. – Jerrodjerrolcurry
at least once, so that the resultingstd::function
owns the arguments. Currently, there's a copy (becausecurry
takes by value), but no copy ends up in the resultingstd::function
-- this means thatstd::forward
ing the arguments makes no sense. In functional programming, there are no side effects (AFAIK), so move semantics itself should not be applied (otherwise, you can't apply that function object twice, e.g.). – Jerrodjerrol&&
when writing it. – Helico&&
(and not the[&]
->[=]
), I get 25 with clang++ but not the expected result with g++ 4.8.2. – Helicocurry
to take an r-value reference tofirst
, or 2) modify the lambda incurry_impl
to capturefirst
by-value instead of by-reference. – Mikey&&
and then takefirst
by value in the capture andmove
it, it seems to work with g++ 4.8.2 and clang++ 3.5. – Helicoapply
or any other function that takes a functionF
and applies it to the elements of a list:F
is called multiple times; ifF
is the result ofcurry
, you might end up moving from the same objects several times. – Jerrodjerrolfirst
tofunc
without anymove
. That may prevent the problem. – Helicostd::bind
. Currying would allow youradd
lambda to be called likeadd(5)(10)
, i.e. make it take one argument and return a new callable. – Nuncompose
function presented here, as it (wrongfully) inspects the input's argument / result types and usesstd::function
. :) To Morwenn: Note how the implementation ofstd.functional
(linked to in the documentation) has changed thecurry
function topartial
, with a deprecated version ofcurry
for backwards compatibility. – Nun