Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are there cases in which trailing-return-type syntax in lambda cannot be avoided?

In relation to a previous question (Is it possible to return an object of type T by reference from a lambda without using trailing return type syntax?), I was wondering if there is any other significant case or example in which trailing-return-type syntax, when using lambdas, can not be avoided.

like image 872
FdeF Avatar asked Feb 06 '23 13:02

FdeF


1 Answers

In C++14, a bit contrived example is the use of sfinae in combination with a generic lambda:

[](auto &&arg)
-> decltype(arg.f(), void())
{ /* do whatever you want */ }

Anyway one could argue that a static_assert suffices:

[](auto &&arg) {
    static_assert(has_metod_f<std::decay_t<decltype(arg)>>::value, "!");
    /* do whatever you want */
}

Where has_method_f is the common detector idiom.
Anyway, imagine a case where you want to construct a composed functor starting from a bunch of lambdas:

#include<utility>
#include<iostream>

template<typename...>
struct Base;

template<typename Func, typename... Others>
struct Base<Func, Others...>: Func, Base<Others...> {
    Base(Func func, Others... others)
    : Func{std::move(func)}, Base<Others...>{std::move(others)...}
    {}

    template<typename... Args>
    auto operator()(int, Args&&... args)
    -> decltype(Func::operator()(std::forward<Args>(args)...)) {
        Func::operator()(std::forward<Args>(args)...);
    }

    template<typename... Args>
    auto operator()(char, Args&&... args) {
        Base<Others...>::operator()(0, std::forward<Args>(args)...);
    }
};

template<>
struct Base<> {
    template<typename... Args>
    auto operator()(Args&&...) {
        std::cout << "fallback" << std::endl;
    }
};

template<typename... Ops>
struct Mixin: Base<Ops...> {
    Mixin(Ops... ops)
        : Base<Ops...>{std::move(ops)...}
    {}

    template<typename... Args>
    auto operator()(Args&&... args) {
        return Base<Ops...>::operator()(0, std::forward<Args>(args)...);
    }
};

struct T { void f() {} };
struct U {};

int main() {
    auto l1 = [](auto &&arg) -> decltype(arg.f(), void()) {
        std::cout << "accept T" << std::endl;
    };

    auto l2 = [](U) {
        std::cout << "accept U" << std::endl;
    };

    Mixin<decltype(l1), decltype(l2)> mixin{std::move(l1), std::move(l2)};
    mixin(T{});
    mixin(U{});
    mixin(0);
}

In this case, a static_assert would prevent the compilation and it is not the expected result.
On the other side, the trailing return type can be used to enable sfinae directly on the lambdas with the help of their wrappers.

like image 148
skypjack Avatar answered Feb 08 '23 15:02

skypjack