У меня есть несколько классов, каждый из которых определяет внутреннюю структуру Result и все они являются производными от класса Base. Я хотел бы иметь один метод реализации в базовом классе, который возвращает структуру Result производного класса при вызове в производном классе. Приведенный ниже код работает, но меня беспокоит дублирование типов в фактическом вызове get_result.
Как я могу заставить компилятор автоматически выводить аргумент шаблона из фактического вызова get_result?
Дополнительные ограничения:
В настоящее время он привязан к C++17, но если есть решение с более новыми стандартными версиями, мне было бы интересно их увидеть.
Обновлено: Некоторые пояснения:
Base существует не только для метода get_result, он сам имеет некоторые свойства, а также используется для полиморфизма, поэтому его нельзя полностью шаблонизировать.
#include <string>
using namespace std;
class Base
{
public:
// Some virtual stuff here that prevents the whole class from being templated. All derived classes need the s
template<typename T>
typename T::Result get_result() const
{
// static_assert(std::is_base_of_v<Base, T>, "Derived not derived from BaseClass");
return T::Deserialize();
}
};
class DerivedA
: public Base
{
public:
struct Result
{
int i = 0;
};
static Result Deserialize(/*In reality there is some stream argument here*/)
{
Result r;
r.i = 42;
return r;
}
};
class DerivedB
: public Base
{
public:
struct Result
{
string s;
};
static Result Deserialize(/*In reality there is some stream argument here*/)
{
Result r;
r.s = "Yatta";
return r;
}
};
int main()
{
{
DerivedA a;
const auto res = a.get_result<DerivedA>();
}
{
DerivedB b;
const auto res = b.get_result<decltype(b)>();
}
return 0;
}
Редактировать 2:
На данный момент лучшим решением в данных обстоятельствах кажется использование бесплатной функции:
template <class T>
typename T::Result get_result(const T& instance)
{
return T::Deserialize();
}
const auto res = get_result(a);





Просто перегрузите функцию в дочерних классах:
class Base
{
public:
template<typename T>
typename T::Result get_result() const
{
// static_assert(std::is_base_of_v<Base, T>, "Derived not derived from BaseClass");
return T::Deserialize();
}
};
class DerivedA
: public Base
{
public:
struct Result
{
int i = 0;
};
static Result Deserialize(/*In reality there is some stream argument here*/)
{
Result r;
r.i = 42;
return r;
}
Result get_result() {
return Base::get_result<DerivedA>();
}
};
class DerivedB
: public Base
{
public:
struct Result
{
string s;
};
static Result Deserialize(/*In reality there is some stream argument here*/)
{
Result r;
r.s = "Yatta";
return r;
}
Result get_result() {
return Base::get_result<DerivedB>();
}
};
Таким образом, вам не придется отправлять тип в коде пользователя:
{
DerivedA a;
const auto res = a.get_result();
}
{
DerivedB b;
const auto res = b.get_result();
}
Другим способом было бы использовать CRTP:
template<typename T>
class Base
{
public:
typename T::Result get_result() const
{
// static_assert(std::is_base_of_v<Base, T>, "Derived not derived from BaseClass");
return T::Deserialize();
}
};
class DerivedA
: public Base<DerivedA>
{
public:
struct Result
{
int i = 0;
};
static Result Deserialize(/*In reality there is some stream argument here*/)
{
Result r;
r.i = 42;
return r;
}
};
class DerivedB
: public Base<DerivedB>
{
public:
struct Result
{
string s;
};
static Result Deserialize(/*In reality there is some stream argument here*/)
{
Result r;
r.s = "Yatta";
return r;
}
};
В этом примере вы можете добавить еще один базовый класс, не являющийся шаблоном, если вам нужен полиморфизм на основе времени выполнения.
Если бы не использовать шаблоны, все реализации get_result были бы одинаковыми, за исключением типа возвращаемого значения. Таким образом, избежание повторения было причиной того, что в первую очередь поместили его в базовый класс. Использование подхода переопределения противоречит этой цели.
@ceph Я не думаю, что использование подхода переопределения противоречит цели. Он удаляет повторение типа при каждом использовании функций за счет добавления одного типа в тип. Также нет дублированной логики, только один уровень пересылки. Я думаю, что оба подхода того стоят. CRTP очень близок к тому, чего хочет OP.
В C++23 вы можете использовать явный объект для вывода параметра типа на основе статического типа объекта, для которого он вызывается: (пример)
template<typename T>
typename T::Result get_result(this const T&)
// ^^^^
{
return T::Deserialize();
}
Вызов его похож на обычный шаблон с вычетом:
const auto res = a.get_result();
Спасибо, это именно то, чего мне хотелось. gcc.godbolt.org/z/KxjWx1cEM для рабочего примера. К сожалению, C++23 все еще недоступен для этого проекта.
@ceph, я действительно поместил туда такую ссылку, но, честно говоря, она выглядела как ссылка на документацию, поэтому я изменил ее, чтобы она была «наименее удивительной».