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

时间:2019-03-10 12:46:07

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

这是关于lambda重载集和完美转发以及对comment的后续跟进的问题。有关如何使用此方法的更多信息,请参见another related question

我对以下代码段有疑问。

问题1:对于lambda重载,我使用的是this post中的overload(Fs...) -> overload<Fs...>,但是随后在this answer中看到了overload(Fs&&...) -> overload<std::decay_t<Fs>...>。这种差异在什么情况下有意义?

问题2:为什么要在下面用identity而不是return decltype(x)(x)来定义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

1 个答案:

答案 0 :(得分:4)

  

这种差异在什么情况下有意义?

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

如果您想使用哪个,那么客观地说,杂乱程度较小的那个更好。使用std::decay_t是为了获得与按值版本相同的行为,因此也可以使用它。

  

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

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

  

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

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