Я только что написал следующий простой код, но он не компилируется:
#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)} {
...
Если я не использую параметр obj в функции запуска, пример работает так, как ожидалось.
@Jarod42 хорошее редактирование, спасибо
@Jarod42 Jarod42 приведет ли написание оператора таким образом к ошибке? ((std::cout << sep << args, sep= ", "), ...); Я просто удалил одну скобку.
Я предпочитаю быть в безопасности с некоторыми приоритетами операторов.





К сожалению
template<typename P>
Obj(P&& param): name{std::forward<P>(param)} { }
слишком жадный, и ловить Obj(Obj&) (для чего это неправильно).
Вы можете SFINAE этот конструктор,
template<typename P>
requires (std::is_constructible_v<std::string, P>)
Obj(P&& param): name{std::forward<P>(param)} { }
или добавить дополнительные перегрузки
Obj(Obj&&) = default;
Obj(Obj&) = default; // To fix the issue
Obj(const Obj&) = default;
или даже проще, поскольку здесь вам не нужен шаблон:
explicit Obj(std::string name): name{std::move(name)} { }
нормально ли, если я определю такой шаблон => template<typename P, typename = std::enable_if_t<!std::is_same_v<P, Obj&>>>
Есть несколько способов SFINAE, std::enable_if до C++20. Состояние может быть адаптировано под ваши нужды.
Проблема в том, что шаблон конструктора
template<typename P>
Obj(P&& param)
примет значение Obj lvalue.
Obj по-прежнему имеет неявно определенный конструктор копирования и конструктор перемещения, но они имеют сигнатуры Obj(const Obj&) и Obj(Obj&&) соответственно, поэтому они проигрывают в разрешении перегрузки, когда передается lvalue типа Obj.
У вас может быть конструктор пересылки, который принимает P&&, но его необходимо правильно ограничить.
Например, std::tuple имеет конструктор, который принимает любой UTypes&&..., но он ограничен тем, что не принимает std::tuple, чтобы конструкторы копирования/перемещения всегда выигрывали.
В 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: Для параметров «will-move-from» передайте X&& и std::move параметр).
print/runможно упростить с помощью C++17 Demo.