Search code examples
c++overload-resolutionperfect-forwardingfunction-templates

Parameter pack and perfect forwarding


I just wrote following simple code but it doesnt compile:

#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;
}

I just used simple parameter pack and perfect forwarding example but gives following error:

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)} {
...

If i dont use the obj paramater in the run function, the example works as expected.


Solution

  • Unfortunately

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

    is too greedy, and catch Obj(Obj&) (for which it is wrong).

    You might SFINAE that constructor,

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

    Demo

    or add extra overloads

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

    Demo

    or even simpler, as you don't need template here:

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

    Demo