Re: Haskell-style "dot" operator
Am 06.03.2012 22:18, schrieb Jimmy H.:
I sought to define a Haskell-style "dot" operator for C++, which would
be operator*.
I wrote:
#include<functional>
#include<iostream>
template<typename A, typename B, typename C>
auto operator*(std::function<A(B)> a, std::function<B(C)> b) ->
std::function<A(C)>
{
return [a,b](C c) { return a(b(c)); };
}
Let me start with a initial warning here: In general I recommend to stay
away from adding operators for types outside of their own namespaces,
because these are only found under very specific and simple situations
as in this code. ADL won't work for example, because the global
namespace is no associated namespace of std::function objects.
int addOne(int a) {
return a + 1;
}
int main() {
std::function<int(int)> addOneFunc(addOne);
std::cout<< (addOneFunc * addOneFunc)(2)<< std::endl;
}
Notably, though, I need to explicitly convert addOne to a
std::function for it to call the custom operator* function. Attempting
to write a helper template to do the conversion for me:
template<typename A, typename B>
auto operator*(A a, B b) ->
decltype(std::function<A>(a) * std::function<B>(b))
{
return std::function<A>(a) * std::function<B>(b);
}
This does not help.
This won't work, because your arguments would never be deduced as
function types. Note that A and B in above signature would be required
to match a type pattern R(Args) which is a function type. What you
provide in the code will be a function pointer R(*)(Args) and that won't
result in successful instantiation of the std::function template.
Is it simply impossible to add custom operators to
built-in types? If that is the case, why can't I write:
int main() {
std::function<int(int)> addOneFunc(addOne);
std::cout<< (addOneFunc * addOne)(2)<< std::endl;
}
That has a std::function in it!
This argument is irrelevant: For any general conversion situation a
compiler cannot deduce for some function template the template parameter
types (thus B and C in std::function<B(C)>) when you provide any
argument (presumably a function pointer B(*)(C) in this example) that is
just *convertible* to the parameter type. Therefore function templates
do not consider any conversions when arguments are provided. The typical
solution out of this problem is to add overloads where either the first
or the second parameter is any valid argument type.
Now it would be rather simple to add one overload that accepts any
callable object on the *left* side of your operator*, thus referring to
an expression of the form
(addOne * addOneFunc)(2)
We just need to define some helper traits to validate that this first
argument can be called with a std::function<B(C)>, which essentially
means that we have determined the argument of the call. Let's write our
trait first:
#include <type_traits>
#include <utility>
template<class F, class... A>
struct callable_traits
{
private:
template<class F1, class... A1>
static auto test(int) ->
decltype(std::declval<F1>()(std::declval<A1>()...));
struct failure {};
template<class...>
static auto test(...) -> failure;
public:
typedef decltype(test<F, A...>(0)) result_type;
static const bool value = !std::is_same<result_type, failure>::value;
};
and let's add an additional operator* overload:
template <typename F, typename B, typename C>
auto operator*(F a, std::function<B(C)> b) ->
typename std::enable_if<callable_traits<F&, B>::value,
std::function<typename callable_traits<F&, B>::result_type(C)>>::type
{
return [a, b](C c) { return a(b(c)); };
}
Notice, that we can deduce all necessary information from the arguments:
The previous return type A is just the return type of our function
object type F and the argument of the call is just the result of the
right std::function<B(C)> object, so it must be some value of type B.
Now try to define a third overload where we accept *any* function object
type on the right side. This will turn out to be a problem that does not
have a unique solution, because you would need to find out the necessary
argument type that makes a function-call valid given some return type.
It is thus not possible *for the general case* for the compiler to find
an unambiguous type C that makes the coupled function call well-formed.
In contrast to a function call expression which provides a mapping from
a given argument type to the return type there does not exist a general
expression that returns the argument type given some return type.
In your scenario a simpler solution works, if you accept to be
restricted to function pointers as argument types. In this case just write:
template <typename A, typename B, typename C>
auto operator*(std::function<A(B)> a, B(*b)(C)) -> std::function<A(C)>
{
return [a, b](C c) { return a(b(c)); };
}
and your call expression
(addOneFunc * addOne)(2)
is deducible again. If you can live with this restriction, you can throw
away the initially suggested callable_traits and define your first
overload as follows:
template <typename A, typename B, typename C>
auto operator*(A(*a)(B), std::function<B(C)> b) -> std::function<A(C)>
{
return [a, b](C c) { return a(b(c)); };
}
HTH & Greetings from Bremen,
Daniel Kr?gler
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]