【问题标题】:Questions on lambda overloads, type conversions and perfect forwarding关于 lambda 重载、类型转换和完美转发的问题
【发布时间】:2019-03-10 12:46:06
【问题描述】:

这是一个关于 lambda 重载集和完美转发的问题,也是comment 的后续问题。有关如何使用它的更多上下文,请参阅another related question

我对下面的代码 sn-p 有一些疑问。

Q1:对于 lambda 重载,我使用的是来自 this postoverload(Fs...) -> overload<Fs...>,但后来在 this answer 中我看到了 overload(Fs&&...) -> overload<std::decay_t<Fs>...>。这种差异在什么情况下是相关的?

Q2:为什么要在下面用return decltype(x)(x) 定义identity 函数,而不仅仅是return x

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

【问题讨论】:

  • 这些都是单独的好问题,但我没有足够的知识来说明是否有一个优雅的统一答案可以一次性应用于所有三个问题。
  • @StoryTeller 我已经考虑过这一点,但我认为这些问题密切相关。只是我指定了我不理解的位,而不是仅仅说“整个事情是如何工作的”?要了解convert(...) w.r.t 参数转发的行为,我们需要了解identity 的行为方式,虽然overload 的情况确实略有不同,但它也与它如何处理它的参数有关。 (所以是的,Q1 可能应该是分开的,但恕我直言,Q2 和 Q3 肯定不是......)。
  • 嗯...看起来@StoryTeller 删除了他原来的评论,说这个问题可能太宽泛了。无论如何,它现在已经回答了(谢谢!),下次我会更仔细地考虑范围。

标签: c++ lambda variadic-templates perfect-forwarding


【解决方案1】:

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

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

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

为什么要在下面使用 return decltype(x)(x) 来定义恒等函数,而不仅仅是 return x

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

我们可以将foo(convert(std::forward&lt;Args&gt;(args))...) 视为完美转发(对于所有未转换的参数),就像foo(std::forward&lt;Args&gt;(args)...) 一样

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

【讨论】:

  • 是否等同于更惯用的[](auto&amp;&amp;x)-&gt;decltype(auto){return std::forward&lt;decltype(x)&gt;(x);};
  • @NikolausDemmel - 是的,我想你可以支持按值参数。它不会影响初始化本身,因为推导指南与它无关。重载解决方案发生两次。一次用于推导指南,另一次用于初始化需要。
  • 我在一些更复杂的推论指南中看到的这种偏爱按值参数的建议。事实上,按价值表现与演绎指南无关,这意味着消除每种类型可能成为参考的可能性可以大大简化事情,通常是 Just Work™。
  • @Oktalist - 我认为这就足够了。但问题是,decltype(x) 是一个右值引用,所以 Clang 拒绝将x(左值)绑定到它。我认为我们应该在这里明确地坚持转发。
  • @NikolausDemmel - 很好,添加了另一个注释。但老实说,我认为你在这一点上过度思考了答案。我认为人们现在可以轻松得出结论,而无需让答案变得更加冗长。
猜你喜欢
  • 2017-08-05
  • 2011-11-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-22
  • 2021-06-17
  • 1970-01-01
  • 2017-01-17
相关资源
最近更新 更多