代码之家  ›  专栏  ›  技术社区  ›  Qwe Qwe

参数包和完美转发

c++
  •  2
  • Qwe Qwe  · 技术社区  · 3 周前

    我只写了以下简单的代码,但它没有编译:

    #include <iostream>
    #include <string>
    
    
    class Obj{
    public:
        std::string name = "Name";
        std::string l_name = "LastName";
        
        template<typename P>
        Obj(P&& param): name{std::forward<P>(param)} { }
        
        friend std::ostream& operator<<(std::ostream& os, const Obj& obj);
    };
    
    std::ostream& operator<<(std::ostream& os, const Obj& obj) {
        os << obj.name << ":" << obj.l_name;
        return os;
    }
    
    
    void print() {
        std::cout << "}";
    }
    
    template<typename T, typename ...Args>
    void print(T param, Args... args) {
        std::size_t count = sizeof...(args);
        std::cout << param;
        if ( count != 0 ) {
            std::cout << ",";
        }
        print(args...);
    }
    
    template<typename... Args>
    void run(Args... args) {
        std::cout << "{";
        print(args...);
    }
    
    int main() {
        Obj obj{"obj"};
        run("1", "2", 1.3, std::string{"Some Message"}, obj);
        
        return 0;
    }
    

    我只是使用了简单的参数包和完美的转发示例,但给出了以下错误:

    main.cpp: In instantiation of ‘Obj::Obj(P&&) [with P = Obj&]’:
    main.cpp:49:8:   required from here
    main.cpp:12:21: error: no matching function for call to ‘std::__cxx11::basic_string::basic_string()’
       12 |     Obj(P&& param): name{std::forward<P>(param)} {
    ...
    

    如果我在run函数中不使用obj参数,那么这个例子就可以正常工作。

    2 回复  |  直到 3 周前
        1
  •  7
  •   Jarod42    3 周前

    不幸地

    template<typename P>
    Obj(P&& param): name{std::forward<P>(param)} { }
    

    太贪婪了 Obj(Obj&) (这是错误的)。

    你可以取消那个构造函数,

    template<typename P>
    requires (std::is_constructible_v<std::string, P>)
    Obj(P&& param): name{std::forward<P>(param)} { }
    

    Demo

    或添加额外的过载

    Obj(Obj&&) = default;
    Obj(Obj&) = default; // To fix the issue
    Obj(const Obj&) = default;
    

    Demo

    或者更简单,因为这里不需要模板:

    explicit Obj(std::string name): name{std::move(name)} { }
    

    Demo

        2
  •  4
  •   Jan Schultke    3 周前

    问题是构造函数模板

    template<typename P>
    Obj(P&& param)
    

    会接受 Obj 左值。 Obj 仍然有一个隐式定义的复制构造函数和移动构造函数,但它们具有签名 Obj(const Obj&) Obj(Obj&&) 因此当类型为的左值 Obj 通过。

    你可以有一个转发构造函数 P&& ,但这需要适当约束。 例如 std::tuple has a constructor 这需要任何 UTypes&&... ,但这被限制为不接受 std::元组 使得复制/移动构造函数总是获胜。

    在C++20及以上版本中,您可以编写

    template<typename P>
      requires !std::convertible_to<P&&, Obj>
    Obj(P&& param)
    

    在旧版本中,您可以使用 std::enable_if 相反

    您还可以通过创建构造函数来完全避免在此处使用模板:

    Obj(std::string&& param) : name(std::move(param)) { }
    

    请注意,您也可以采取 std::string 按价值计算,但通常建议不要这样做(请参阅 CppCoreGuidelines F.18: For “will-move-from” parameters, pass by X&& and std::move the parameter ).