Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Questions on lambda overloads, type conversions and perfect forwarding

Tags:

This is a question on lambda overload sets and perfect forwarding and somewhat of a followup to a comment. For more context of how this is used see another related question.

I have some questions on the below code snippet.

Q1: For lambda overloads, I was using overload(Fs...) -> overload<Fs...> from this post, but then in this answer I saw overload(Fs&&...) -> overload<std::decay_t<Fs>...>. In what situations is this difference relevant?

Q2: Why would you want to define the identity function below with return decltype(x)(x) and not just return x?

Q3: Can we consider foo(convert(std::forward<Args>(args))...) as perfect forwarding (for all not-converted arguments) just like foo(std::forward<Args>(args)...)?

#include <utility>
#include <iostream>


/////////////////////////////////////////////////////////////////////////////////


struct Foo {
    virtual ~Foo() = default;
};

struct FooA: public Foo {
    static void foo(const FooA&, int) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
};

struct FooB: public Foo {
    static void foo(int, const FooB&) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
};


/////////////////////////////////////////////////////////////////////////////////


template<class...Fs>
struct overload:Fs... {
    using Fs::operator()...;
};

// Q1: In what situations is needed over `overload(Fs...) -> overload<Fs...>`?
template<class...Fs>
overload(Fs&&...) -> overload<std::decay_t<Fs>...>;


/////////////////////////////////////////////////////////////////////////////////


// Q2: What is the purpose of `return decltype(x)(x)` over `return x`?
auto identity=[](auto&&x)->decltype(x){return decltype(x)(x);};

template<typename SpecificFoo, typename... Args>
void bar(Args&&... args) {
  auto convert = overload{
    [](const Foo& f){return dynamic_cast<const SpecificFoo&>(f);},
    identity
  };

  // Q3: Is our definition of `convert` "perfectly forwarding", like if we just called 
  // `foo(std::forward<Args>(args)...)`, or in what situations might this not do the 
  // same thing (for not-converted types)?
  SpecificFoo::foo(convert(std::forward<Args>(args))...);
}


/////////////////////////////////////////////////////////////////////////////////


int main() {
    {
        FooA specific_foo;
        const Foo& foo {specific_foo};
        // assume we only have access to foo when calling bar
        bar<FooA>(foo, 23);
    }
    {
        FooB specific_foo;
        const Foo& foo {specific_foo};
        // assume we only have access to foo when calling bar
        bar<FooB>(42, foo);
    }
}

run it

like image 539
Nikolaus Demmel Avatar asked Mar 10 '19 12:03

Nikolaus Demmel


1 Answers

In what situations is this difference relevant?

When at least one argument is in fact an lvalue (like identity, in fact). In which case the corresponding Fi is a T&, i.e. an lvalue reference. And one can't list an lvalue reference as a base of any class, so std::decay is required to remove all reference and cv-qualifiers. When the deduction guide takes arguments by value, it's automatically a non-issue. This is because template argument deduction for value types already "decays" the types.

If you wonder which one to use, then I'd say the one with less clutter is objectively better. The use of std::decay_t is for getting the same behavior one would get with the by-value version, so one may as well use that.

Why would you want to define the identity function below with return decltype(x)(x) and not just return x

This is a form of forwarding. Since the return type of the lambda is declared to be decltype(x), we need the cast to make sure it binds correctly to an rvalue reference. Because in the case decltype(x) = T&&, it will not bind to x alone, which is an lvalue.

Can we consider foo(convert(std::forward<Args>(args))...) as perfect forwarding (for all not-converted arguments) just like foo(std::forward<Args>(args)...)

Yes. The arguments to bar already bound to references. convert lets those references pass through with value category preserved, so it's forwarding indeed.

like image 133
StoryTeller - Unslander Monica Avatar answered Oct 20 '22 19:10

StoryTeller - Unslander Monica