关于 lambda 重载、类型转换和完美转发的问题

Questions on lambda overloads, type conversions and perfect forwarding

这是一个关于 lambda 重载集和完美转发的问题,是 . For more context of how this is used see .

的后续问题

我对下面的代码片段有一些疑问。

Q1:对于 lambda 重载,我使用 this post, but then in 中的 overload(Fs...) -> overload<Fs...> 我看到 overload(Fs&&...) -> overload<std::decay_t<Fs>...>。这种差异在什么情况下相关?

问题 2:为什么要用 return decltype(x)(x) 而不仅仅是 return x 来定义下面的 identity 函数?

Q3:我们是否可以将 foo(convert(std::forward<Args>(args))...) 视为完美转发(对于所有未转换的参数)就像 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

In what situations is this difference relevant?

当至少一个参数实际上是一个左值时(实际上像 identity)。在这种情况下,相应的 FiT&,即左值引用。并且不能将左值引用列为任何 class 的基础,因此需要 std::decay 来删除所有引用和 cv 限定符。当演绎指南按值获取参数时,它自动就不是问题了。这是因为值类型的模板参数推导已经 "decays" 类型。

如果您想知道该使用哪一个,那么客观上我会说混乱较少的那个更好。使用 std::decay_t 是为了获得与按值版本相同的行为,因此也可以使用它。

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

这是一种转发形式。由于 lambda 的 return 类型声明为 decltype(x),我们需要强制转换以确保它正确绑定到右值引用。因为在 decltype(x) = T&& 的情况下,它不会单独绑定到 x,这是一个左值。

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

是的。 bar 的参数已经绑定到引用。 convert 让那些引用通过并保留值类别,所以它确实在转发。