Reputation: 11716
I would like to be able to determine at compile time, given a generic lambda type, whether it can be invoked with a given set of parameter types. I have the following example C++14 implementation:
#include <iostream>
// helper function; this overload handles the case that the call is possible
// use SFINAE with the extra template parameter to remove this from consideration when the
// call is ill-formed
template <typename Func, typename... Args, typename = decltype(std::declval<Func>()(std::declval<Args>()...))>
auto eval(Func f, Args &&... args) { return f(args...); }
// special type returned from `eval()` when the call can't be done
struct invalid_call { };
// helper function; this overload handles the case that the call is not possible
template <typename Func>
invalid_call eval(Func f, ...) { return invalid_call{}; };
// bring in std::negation from C++17 to help create the below trait
template<class B>
struct negation : std::integral_constant<bool, !bool(B::value)> { };
// trait that determines whether `Func` can be invoked with an argument list of types `Args...`
template <typename Func, typename... Args>
using can_call = negation<std::is_same<decltype(eval(std::declval<Func>(), std::declval<Args>()...)), invalid_call>>;
// arbitary type that has no `operator+`
struct foo {};
int main()
{
auto func = [](auto a1, auto a2) -> decltype(a1 + a2) { return a1 + a2; };
using FuncType = decltype(func);
std::cout << "can call with (int, int): " << can_call<FuncType, int, int>::value << std::endl;
std::cout << "can call with (foo, foo): " << can_call<FuncType, foo, foo>::value << std::endl;
}
This example works fine as-is. What I don't like is the cumbersome way that the lambda must be declared:
auto func = [](auto a1, auto a2) -> decltype(a1 + a2) { return a1 + a2; };
That is, the trailing return type must be specified because C++14's deduced return types don't work with SFINAE. Return type deduction requires substitution of the argument list types into the callable's template call operator, and the program is ill-formed if an error occurs there.
Ideally, I would be able to do the following:
auto func = [](auto a1, auto a2) { return a1 + a2; };
and let the return type work itself out automatically; this would be the most intuitive interface to provide to my users. This is a very simple example, so the argument to the decltype()
doesn't look bad, but in practice, the lambda might be several statements, which wouldn't work with this approach. So my question is:
Using any modern C++ techniques (C++14 would be best, but I'm open to newer features as well if needed), is there any way I can test at compile time whether a generic lambda can possibly be invoked with an arbitrary list of parameter types?
Upvotes: 4
Views: 312
Reputation: 275740
Sure, using the C++98 feature of macros
#define RETURNS(...) noexcept(noexcept(__VA_ARGS__))->decltype(__VA_ARGS__) { return __VA_ARGS__; }
then
auto func = [](auto a1, auto a2) RETURNS(a1+a2);
does it.
There is a C++20 proposal by our very own @Barry that makes this
auto func = [](auto a1, auto a2) => a1+a2;
without the use of macros.
In general, it is not possible, nor is it intended to be possible, to force the body of a function or lambda to be compiled to determine if a SFINAE expression is acceptable or not. Such errors are supposed to be hard, as this simplifies the work of C++ compilers; they don't have to be able to compile entire bodies of arbitrary functions and then cleanly backout to an errorless state while determining if overload resolution succeeds or not.
In the case of more than one return statement or a long set of complex types used in the return statement, you are out of luck. Write the decltype
. Pray you get it right.
Upvotes: 3