9

Can generic lambdas take advantage of the "Substitution Failure Is Not An Error" rule ? Example

auto gL = 
    [](auto&& func, auto&& param1, auto&&... params) 
        -> enable_if_t< is_integral<
            std::decay_t<decltype(param1)>
        >::value>
    {
        // ...
    };

auto gL =  
     [](auto&& func, auto&& param1, auto&&... params) 
        -> enable_if_t< !is_integral<
            std::decay_t<decltype(param1)>
        >::value>
    {
        // ...
    };

Are there any workarounds or plans to include this in the language ? Also since generic lambdas are templated function objects under the hood isn't it a bit odd that this can't be done ?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
  • Your lambda seems to [work just fine](http://ideone.com/rFOkmA) as written? – Kerrek SB Jul 06 '15 at 19:20
  • @KerrekSB OK that's awesome, should I rephrase the question to focus on the SFINAE bit ? – Nikos Athanasiou Jul 06 '15 at 19:21
  • 6
    I have no idea what problem you need to solve... – Kerrek SB Jul 06 '15 at 19:23
  • There shouldn't be any problem if you're simply returning the result of the function call, even if the function returns `void`. The problem arises when you need to store the return value temporarily, do something else, and then return it (or not return anything in case of `void` return type). In that case you can probably work around it by creating an RAII wrapper that does the *do something else* part in its destructor. Or tag dispatch to other lambdas/functions based on `result_of`. – Praetorian Jul 06 '15 at 19:24
  • @Praetorian I was just writing this exact thing. Modified the example. The question is about **using SFINAE with generic lambdas**. Can it be done, will it be done, why can't it, shouldn't it ? ... – Nikos Athanasiou Jul 06 '15 at 19:26
  • @RyanHaining What does the return type have to do with benchmarking ? – Nikos Athanasiou Jul 06 '15 at 19:30
  • 1
    `Timer t; return f(args...);`, and `Timer::~Timer` prints the result. – Kerrek SB Jul 06 '15 at 19:30
  • I misunderstood the comments, I read it as `func` returning some `timer` which is assigned to `k` – Ryan Haining Jul 06 '15 at 19:31
  • you want to SFINAE without overloading? – Ryan Haining Jul 06 '15 at 19:42
  • 1
    sfinae works only when there is a better overload to pick when the first one errors. Since you can't overload lambdas it is not clear what sfinae will achieve. – Daniel Jul 06 '15 at 19:45
  • @Dani this is a perfect reason (also pointed out by Ryan). I was caught up on the _"it's all templated function object under the hood"_ rationale that I forgot about overloading lamdas. I don't understand why don't you post it as an answer – Nikos Athanasiou Jul 06 '15 at 19:49
  • You're allowed to use expression SFINAE in the trailing return type, [here's](http://coliru.stacked-crooked.com/a/a24f8405dd8f9612) a modified version of Kerrek's example that rejects `void` return type. What you cannot do is provide a complementary version that only works for `void` return type. – Praetorian Jul 06 '15 at 19:49
  • @Yakk I beg to disagree. Adding bolg gives emphasis (greek έμφασις = εν + φαινομαι = make something appear). Please re-read the question. This is not an implementation problem but a question about the language and a feature (SFINAE for generic lambdas) – Nikos Athanasiou Jul 06 '15 at 20:00
  • Much better! Comments deleted, vote reversed. – Yakk - Adam Nevraumont Jul 06 '15 at 20:47

3 Answers3

10

Lambdas are function objects under the hood. Generic lambdas are function objects with template operator()s.

template<class...Fs>
struct funcs_t{};

template<class F0, class...Fs>
struct funcs_t<F0, Fs...>: F0, funcs_t<Fs...> {
  funcs_t(F0 f0, Fs... fs):
    F0(std::move(f0)),
    funcs_t<Fs...>(std::move(fs)...)
  {}
  using F0::operator();
  using funcs_t<Fs...>::operator();
};
template<class F>
struct funcs_t<F>:F {
  funcs_t(F f):F(std::move(f)){};
  using F::operator();
};
template<class...Fs>
funcs_t< std::decay_t<Fs>... > funcs(Fs&&...fs) {
  return {std::forward<Fs>(fs)...};
}

auto f_all = funcs( f1, f2 ) generates an object that is an overload of both f1 and f2.

auto g_integral = 
  [](auto&& func, auto&& param1, auto&&... params) 
    -> std::enable_if_t< std::is_integral<
        std::decay_t<decltype(param1)>
    >{}>
  {
    // ...
  };

auto g_not_integral =  
 [](auto&& func, auto&& param1, auto&&... params) 
    -> std::enable_if_t< !std::is_integral<
        std::decay_t<decltype(param1)>
    >{}>
{
    // ...
};

auto gL = funcs( g_not_integral, g_integral );

and calling gL will do SFINAE friendly overload resolution on the two lambdas.

The above does some spurious moves, which could be avoided, in the linear inheritance of funcs_t. In an industrial quality library, I might make the inheritance binary rather than linear (to limit instantiation depth of templates, and the depth of the inheritance tree).


As an aside, there are 4 reasons I know of to SFINAE enable lambdas.

First, with new std::function, you can overload a function on multiple different callback signatures.

Second, the above trick.

Third, currying a function object where it evaluates when it has the right number and type of args.

Forth, automatic tuple unpacking and similar. If I'm using continuation passing style, I can ask the passed in continuation if it will accept the tuple unpacked, or the future unbundled, etc.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Now I remember posting about [creating overload sets for lambda functions](http://stackoverflow.com/a/22147375/2567683) (equally poor received). Sorry for the confusion, I should have phrased my question better from the beggining, this is a nice topic and it's a pitty it'll be overlooked – Nikos Athanasiou Jul 06 '15 at 21:45
2

The use of SFINAE is to remove an overload or a specialization from the candidate set when resolving a given function or template. In your case, we have a lambda - that is a functor with a single operator(). There is no overload, so there is no reason to use SFINAE1. The fact that the lambda is generic, which makes its operator() a function template, doesn't change that fact.

However, you don't actually need to differentiate between different return types. If func returns void for the given arguments, you can still return it. You just can't assign it to a temporary. But you don't have to do that either:

auto time_func = [](auto&& func, auto&&... params) {
    RaiiTimer t;
    return std::forward<decltype(func)>(func)(
        std::forward<decltype(params)>(params)...); 
};

Just write an RaiiTimer whose constructor starts a timer and whose destructor stops it and prints the result. This will work regardless of func's return type.

If you need something more complicated than that, then this is one of those cases where you should prefer a functor over a lambda.


1Actually, as Yakk points out, SFINAE could still be quite handy to check if your function is callable period, which isn't the problem you're trying to solve - so in this case, still not very helpful.

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • A SFINAE enabled lambda can be tested against (can I call it with some stuff or not?), so it is quite useful. As an example, I can take a SFINAE enhanced lambda and pass it to a [`curry(F)`](http://stackoverflow.com/q/26655685/1774667) function, and have it invoke when I have passed enough parameters. Or I could collect a bunch of lambdas, aggregate them into one object, and use SFINAE to determine which one I should dispatch a call to (an overload set of lambdas). SFINAE lamdas have *early failure*, which is useful even outside of overload resolution. – Yakk - Adam Nevraumont Jul 06 '15 at 20:13
  • ... How do you know what problem the OP is trying to solve? Can you understand the question beyond the title? How is that possible? – Yakk - Adam Nevraumont Jul 06 '15 at 20:22
  • @Yakk I think he just wants to to time any `func` and have `gL` return the result (or not)? – Barry Jul 06 '15 at 20:26
2

A generic lambda can only have one body, so SFINAE wouldn't be of much use here.

One solution would be to package the call into a class which can store the result and is specialized on a void return type, encapsulating the void special handling away from your lambda. With a very little overhead, you can do this using the thread library facilities:

auto gL = 
    [](auto&& func, auto&&... params)
    {
        // start a timer
        using Ret = decltype(std::forward<decltype(func)>(func)(
            std::forward<decltype(params)>(params)...));
        std::packaged_task<Ret()> task{[&]{
            return std::forward<decltype(func)>(func)(
                std::forward<decltype(params)>(params)...); }};
        auto fut = task.get_future();
        task();
        // stop timer and print elapsed time
        return fut.get(); 
    };

If you want to avoid the overhead of packaged_task and future, it's easy to write your own version:

template<class T>
struct Result
{
    template<class F, class... A> Result(F&& f, A&&... args)
        : t{std::forward<F>(f)(std::forward<A>(args)...)} {}
    T t;
    T&& get() { return std::move(t); }
};
template<>
struct Result<void>
{
    template<class F, class... A> Result(F&& f, A&&... args)
        { std::forward<F>(f)(std::forward<A>(args)...); }
    void get() {}
};

auto gL = 
    [](auto&& func, auto&&... params)
    {
        // start a timer
        using Ret = decltype(std::forward<decltype(func)>(func)(
            std::forward<decltype(params)>(params)...));
        Result<Ret> k{std::forward<decltype(func)>(func),
            std::forward<decltype(params)>(params)...};
        // stop timer and print elapsed time
        return k.get(); 
    };
ecatmur
  • 152,476
  • 27
  • 293
  • 366