Я пытаюсь реализовать следующий API:
geojson::position_t<> point_1{ 10, 12 };
geojson::position_t<> point_2( 11, 13 );
geojson::position_t<> point_3(std::pair<int, int>(12, 14));
geojson::position_t<> line_1{
geojson::position_t<>{ 100, 120 },
geojson::position_t<>{ 110, 130 } };
geojson::position_t<> poly_1{
{ geojson::position_t<>{ 100, 120 }, geojson::position_t<>{ 110, 130 }},
{ geojson::position_t<>{ 101, 121 }, geojson::position_t<>{ 111, 131 }} };
Идея состоит в том, чтобы иметь класс шаблона position_t <> со следующими свойствами:
value_type, определяющий, является ли это точечным или
линияvalue_type get() const {...}, возвращающий точку или тип линии
в зависимости от того, был ли вызван точечный или линейный ctorМой первый подход заключался в использовании boost :: variant, но я застрял в том, как получить value_type и реализовать метод get().
Вторая попытка заключалась в использовании частичной специализации шаблона. Но пока у меня не получилось.
Может ли кто-нибудь предложить подход, как получить требуемый API?
Это невозможно. Если value_type находится во время компиляции, вам нужно получить 2 разных класса.
Хотел бы API быть таким? Обычно такие вещи реализуются как класс Point и абстрактный класс Shape, хранящий контейнер точек, а также классы Line, Rect и т. д., Унаследованные от Shape.
Почему вы хотите, чтобы один тип в разных обстоятельствах вел себя так, как если бы он принадлежал к нескольким типам?
Что ж, может у меня действительно неправильный дизайн и требования. Я не хотел реализовывать полиморфные классы времени выполнения и хотел добиться результата, используя правила вывода типа SFINAE /.
Можете ли вы использовать C++ 17? Затем вы можете использовать вычет для position_t<point> по сравнению с position_t<line>.
Да, C++ 17 мне подходит.





Вот два дополнительных решения:
Первый использует стандартный полиморфизм, он использует базовый класс GraphicBase.
Второй подход - использовать std :: variant для хранения объектов. Если это используется, вы можете удалить базовый класс и виртуальные функции. Как видно из вашего комментария, вы видите в правилах вывода типа решение. Да, мой пример работает именно так!
У него есть некоторые плюсы и минусы для полиморфизма vtable по сравнению с помеченным объединением как std :: variant. И то, и другое возможно!
Замечание по поводу:
have some internal value_type identifying whether it is point or line
Вам не нужен какой-либо дополнительный идентификатор, потому что сам вариант уже содержит этот тег типа. Вариант - это просто объединение и дополнительная запись данных, которая отслеживает фактический присвоенный тип. Именно то, что вы хотите! Вы можете использовать этот тег с std::visit для вызова любой функции, которую соответствующий тип передал переданному вами объекту функции. (В моем примере я использую общую лямбду, чтобы упростить отправку.
Намекать: Не думайте о SFINAE, если возможна простая перегрузка или специализация!
class GraphicBase
{
public:
virtual void Print()=0;
// for using with vtable polymorphism
GraphicBase* Get() { return this; }
};
class Point: public GraphicBase
{
public:
Point( int, int ){}
void Print() override { std::cout << "Point" << std::endl; }
void PrintNonVirtual() { std::cout << "Point" << std::endl; }
};
class Line: public GraphicBase
{
public:
Line( Point, Point ){}
void Print() override { std::cout << "Line" << std::endl; }
void PrintNonVirtual() { std::cout << "Line" << std::endl; }
};
template < typename T >
class Graphics: public T
{
public:
using T::T;
// for using with variants
T GetNonVirtual() { return static_cast<T>(*this);}
};
Graphics( int, int ) -> Graphics<Point>;
Graphics( Point, Point ) -> Graphics<Line>;
int main()
{
Graphics p1(1,1);
Graphics l1({1,2},{3,4});
// using vtable polymorphism
std::vector<GraphicBase*> v;
v.push_back( &p1 );
v.push_back( &l1 );
for ( auto el: v ) el->Print();
// using the Get() to get the base pointer
GraphicBase* ptr;
ptr = p1.Get();
ptr->Print();
ptr = l1.Get();
ptr->Print();
// or using variants:
using VT = std::variant< Point, Line >;
std::vector<VT> var;
var.push_back( p1 );
var.push_back( l1 );
for ( auto& el: var )
{
std::visit( []( auto& v ){ v.PrintNonVirtual(); }, el );
}
// here we get a copy of the object ( references not allowed in variants! )
VT va1 = p1.GetNonVirtual();
VT va2 = p1.GetNonVirtual();
std::visit( []( auto& v ){ v.PrintNonVirtual(); }, va1 );
std::visit( []( auto& v ){ v.PrintNonVirtual(); }, va2 );
}
В C++ 17 вы можете использовать выведенное руководство, поэтому у вас есть как position_t<Point>, так и position_t<Line>, и в соответствии с конструктором параметров выберите правильный.
Что-то вроде:
class Point
{
public:
int x;
int y;
};
class Line
{
public:
Point start;
Point end;
};
template <typename T> class position_t;
template <>
class position_t<Point>
{
public:
position_t(int x, int y) : point{x, y} {}
position_t(const std::pair<int, int>& p) : point{p.first, p.second} {}
const Point& get() const { return point; }
private:
Point point;
};
template <>
class position_t<Line>
{
public:
position_t(const position_t<Point>& start,
const position_t<Point>& end)
: line{start.get(), end.get()}
{}
const Line& get() const { return line; }
private:
Line line;
};
А затем руководство по дедукции
position_t(int, int) -> position_t<Point>;
position_t(std::pair<int, int>) -> position_t<Point>;
position_t(const position_t<Point>&, const position_t<Point>&) -> position_t<Line>;
Так:
geojson::position_t point_1{ 10, 12 }; // geojson::position_t<Point>
geojson::position_t point_2( 11, 13 ); // geojson::position_t<Point>
geojson::position_t point_3(std::pair<int, int>(12, 14)); // geojson::position_t<Point>
geojson::position_t line_1{ // geojson::position_t<Line>
geojson::position_t{ 100, 120 }, // geojson::position_t<Point>
geojson::position_t{ 110, 130 } }; // geojson::position_t<Point>
Например, большое спасибо - это то, что я искал.
Почему вы хотите, чтобы это был один класс, вы хотите хранить его в контейнере std?