有关Lambda重载,类型转换和完美转发的问题

Nik*_*mel 5 c++ lambda variadic-templates perfect-forwarding

这是关于lambda重载集和完美转发的问题,并且是评论的后续内容。有关如何使用它的更多上下文,请参阅另一个相关问题

我对以下代码段有一些疑问。

Q1:对于拉姆达过载,我用的是overload(Fs...) -> overload<Fs...>这个帖子,但随后这个答案我看到了overload(Fs&&...) -> overload<std::decay_t<Fs>...>。这种差异在什么情况下有意义?

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

问题3:我们可以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 Code Online (Sandbox Code Playgroud)

运行

Sto*_*ica 4

这种差异在什么情况下相关?

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

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

为什么你想要定义下面的恒等函数 return decltype(x)(x) 而不仅仅是 return x

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

我们可以认为foo(convert(std::forward<Args>(args))...)完美转发(对于所有未转换的参数)就像foo(std::forward<Args>(args)...)

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

  • @Oktalist - 我认为这就足够了。但问题是,“decltype(x)”是一个右值引用,因此 Clang 拒绝将“x”(左值)绑定到它。我认为我们应该坚持在这里明确转发。 (2认同)