Starting from this question (Is it possible to figure out the parameter type and return type of a lambda?) I used the proposed function_traits
a lot. However, with C++14 polymorphic lambdas have arrived and they gave me a hard time.
template <typename T>
struct function_traits
: public function_traits<decltype(&T::operator())>
{};
// For generic types, directly use the result of the signature of its 'operator()'
template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
// we specialize for pointers to member function
{
enum { arity = sizeof...(Args) };
// arity is the number of arguments.
typedef ReturnType result_type;
template <size_t i>
struct arg
{
typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
// the i-th argument is equivalent to the i-th tuple element of a tuple
// composed of those arguments.
};
};
The operator()
as proposed in the other question's answer is now overloaded as stated in the standard to support:
auto lambda1 = [](auto& a) { a.foo(); }
and
auto lambda2 = [](auto&&... args) { foo(args...); };
This overloading now rips the function_traits
class apart because the compiler cannot resolve the correct version of operator()
.
lambda.cpp:98:38: error: reference to overloaded function could not be
resolved; did you mean to call it?
typedef function_traits<decltype(&T::operator())> caller;
Is it somehow possible to achieve the functionality of function_traits
on polymorphic lambdas with C++14?
A type lambda lets one express a higher-kinded type directly, without a type definition. For instance, the type above defines a binary type constructor, which maps arguments X and Y to Map[Y, X] . Type parameters of type lambdas can have bounds, but they cannot carry + or - variance annotations.
Use auto and decltype to declare a function template whose return type depends on the types of its template arguments. Or, use auto and decltype to declare a function template that wraps a call to another function, and then returns the return type of the wrapped function.
Generic lambdas were introduced in C++14 . Simply, the closure type defined by the lambda expression will have a templated call operator rather than the regular, non-template call operator of C++11 's lambdas (of course, when auto appears at least once in the parameter list).
CPP. Note: Lambda expressions are available after C++ 11.
This is currently impossible to do in the general sense, but if your lambdas have auto for all of their parameters and will work with all of them being replaced with some known type, you can modify the answer to this question: C++ metafunction to determine whether a type is callable
Along the lines of
static const bool OneArg = (sizeof( test<T, int>(0) ) == 1);
static const bool TwoArg = (sizeof( test<T, int, int>(0) ) == 1);
static const bool ThreeArg = (sizeof( test<T, int, int, int>(0) ) == 1);
static constexpr std::size_t TemplatedOperatorArgSize =
OneArg
? 1
: TwoArg
? 2
: ThreeArg
? 3
: -1;
Apologies for the nested ternaries. And then use something like this to call it:
template<size_t N, typename T>
struct Apply {
template<typename F, typename... A>
static inline decltype(auto) apply(F && f, A &&... a) {
return Apply<N-1, T>::apply(::std::forward<F>(f), std::declval<T>(), ::std::forward<A>(a)...);
}
};
template<typename T>
struct Apply<0, T> {
template<typename F, typename... A>
static inline decltype(auto) apply(F && f, A &&... a) {
return invoke(std::forward<F>(f), std::forward<A>(a)...);
}
};
template<std::size_t Size, typename T, typename F>
inline decltype(auto) apply(F && f) {
return Apply<Size, T>::apply(::std::forward<F>(f));
}
Using invoke from: http://en.cppreference.com/w/cpp/utility/functional/invoke
Get the return type
using return_type = decltype(
apply<has_callable_operator<T>::TemplatedOperatorArgSize, int>(function)
);
The first part could be rewritten to deal with an arbitrary number of arguments using std::make_index_sequence
But generally, I'd say you're going down a rabbit hole. Good luck.
Note: Didn't test the apply code, but it should be good enough to get you going.
Edit: Also, the return type needs to be independent of the parameter types to achieve what you want
I had a similar case when implementing a Cache with a Fetch function. Something like:
template<class KEY, class VALUE, class FetchFunctor>
class Cache { ... };
I wanted to save the user the need to state the type of his/her FetchFunctor and without the C++17 class template argument deduction I went for a createCahce helper method like this:
// [1]
template<class KEY, class VALUE, class FetchFunctor>
auto createCache(FetchFunctor fetchFunctor) {
return Cache<KEY, VALUE, FetchFunctor>(fetchFunctor);
}
Thus creating the cache is quite easy, e.g.:
auto cache = createCache<int, int>([](int i){return i+3;});
It would be much nicer to allow the user creating a cache without the need to provide the Key and Value types of the cache and to deduce them both from the provided FetchFunctor. So I added an additional createCache method:
// [2]
template<class FetchFunctor>
auto createCache(FetchFunctor fetchFunctor) {
// function_traits is a namespace where I 'hide' the
// traits structs for result type and arguments type deduction
using f = function_traits::traits<decltype(fetchFunctor)>;
using KEY = typename f::template arg<0>::type;
using VALUE = typename f::result_type;
return Cache<KEY, VALUE, FetchFunctor>(fetchFunctor);
}
Now creating the cache is even easier:
auto cache = createCache([](int i){return i+3;});
Luckily yes.
This way we can support all the below cases:
// [a]
auto cache = createCache([](int i){return i+3;});
// compiler deduces Key, Value to be: int, int - using the 2nd createCache
// [b]
auto cache = createCache<int, int>([](auto i){return i+3;});
// we have a generic lambda so we provide Key and Value - using the 1st createCache
// [c]
auto cache = createCache<string, string>(
[](const char* s){return string(s) + '!';}
);
// we want different Key and/or Value than would be deduced - we use 1st createCache
The following will not compile:
auto cache = createCache([](auto i){return i+3;});
// we cannot deduce types for generic lambda
// compiler goes to the 2nd createCache but Key and Value cannot be deduced
// - compilation error
But this shouldn't bother us... you can provide Key and Value as in [b] above.
http://coliru.stacked-crooked.com/a/e19151a5c245d7c3
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With