Массив концептуальных указателей

Я пытаюсь выяснить, могу ли я использовать концепции как своего рода интерфейс для классов, не требуя дополнительной нагрузки на виртуальную таблицу. Я собрал пример, который вроде работает, но я должен хранить экземпляры моего класса в массиве, определяемом их общим наследованием, а не их общей концепцией. Я не вижу ничего обсуждаемого в постах о массивах понятий, но g ++ 6.3.0, похоже, не позволяет этого. Ошибка:

$ g++ -fconcepts -std=c++1z custom_concept.cpp
custom_concept.cpp: In function ‘int main()’:
custom_concept.cpp:37:20: error: ‘shapes’ declared as array of ‘IShape*’
IShape* shapes[2] = {&square, &rect};  // doesn't work
^
custom_concept.cpp:39:25: error: ‘shapes’ was not declared in this scope
for (IShape* shape : shapes )
^~~~~~

Если я изменю IShape* массив к Rectangle* массив (как в строке с комментариями ниже той, которая вызвала первую ошибку), программа компилируется и запускается, как и ожидалось.

Почему массив указателей концептов не разрешен? Будет ли это возможно в будущей версии с ++?

(Мой пример включает в себя виртуальные функции и наследование, хотя моя цель состояла в том, чтобы устранить их. Я включил их только для удобства, чтобы получить Rectangle* версия для работы. Если я могу получить IShape* Версия для работы, я планирую убрать виртуальные функции и наследство.)

Вот код:

#include <iostream>

template <typename T>
concept bool IShape = requires (T x, T z, int y)
{
{ T() } ;
{ T(x) }  ;
{ x = z } -> T& ;
{ x.countSides() } -> int ;
{ x.sideLength(y) } -> int ;
};

struct Rectangle
{
Rectangle() {};
Rectangle(const Rectangle& other) {};
Rectangle& operator=(Rectangle& other) {return *this; };
virtual std::string getName() { return "Rectangle"; }

int countSides() {return 4;}
virtual int sideLength(int side) { return (side % 2 == 0) ? 10 : 5; }
};

struct Square : public Rectangle
{
Square() {};
Square(const Square& other) {};
Square& operator=(Square& other) {return *this; };
std::string getName() override { return "Square"; }
int sideLength(int side) override { return 10; }
};

int main()
{
Square square;
Rectangle rect;
IShape* shapes[2] = {&square, &rect};  // doesn't work
//  Rectangle* shapes[2] = {&square, &rect}; // works
for (IShape* shape : shapes )
{
for (int side = 0 ; side < shape->countSides() ; ++side )
{
std::cout << shape->getName() << " side=" << shape->sideLength(side) << "\n";
}
}

return 0;
};

Спасибо @Yakk за идею использования кортежа. G ++ 6.3.0 не полностью реализовал файл #include для включения apply (), как определено стандартом C ++ 17, но он был доступен в std :: экспериментальный. (Я думаю, что это может быть добавлено в более поздней версии g ++.) Вот что я закончил:

#include <iostream>
#include <tuple>
#include <experimental/tuple>

template <typename T>
concept bool IShape = requires (T x, T z, int y)
{
{ T() } ;
{ x = z } -> T& ;
{ T(x) }  ;
{ x.countSides() } -> int ;
{ x.sideLength(y) } -> int ;
};

struct Rectangle
{
Rectangle() {};
Rectangle(const Rectangle& other) {};
Rectangle& operator=(Rectangle& other) {return *this; };

std::string getName() { return "Rectangle"; }
int countSides() {return 4;}
int sideLength(int side) { return (side % 2 == 0) ? 10 : 5; }
};

struct Square
{
Square() {};
Square(const Square& other) {};
Square& operator=(Square& other) {return *this; };

std::string getName() { return "Square"; }
int countSides() {return 4;}
int sideLength(int side) { return 10; }
};

void print(IShape& shape)
{
for (int side = 0 ; side < shape.countSides() ; ++side )
{
std::cout << shape.getName() << " side=" << shape.sideLength(side) << "\n";
}
};

int main()
{
Square square;
Rectangle rect;
auto shapes = std::make_tuple(square, rect);
std::experimental::apply([](auto&... shape) { ((print(shape)), ...); }, shapes) ;

return 0;
};

6

Решение

Это не может быть сделано.

Я имею в виду, что вы можете реализовать свое собственное стирание типов, которое заменяет таблицы функций virtusl. И это, возможно, может быть более производительным, чем vtable в вашем конкретном случае, потому что вы можете использовать его для своей конкретной задачи.

Чтобы получить помощь от компилятора, чтобы вам не приходилось писать шаблонный / клеевой код, вам понадобится поддержка рефлексии и реификации наряду с побочными концепциями.

Если бы вы сделали это, это выглядело бы так:

ShapePtr shapes[2] = {&square, &rect};

или же

ShapeValue shapes[2] = {square, rect};

Теперь это не будет делать все, что вы надеетесь на производительность; стирание типов все еще будет прыгать через указатели на функции. И для каждого объекта или просмотра хранилища накладных расходов. Однако вы можете обменять больше памяти на меньшее количество косвенных ссылок.

Стирание типа вручную здесь в основном реализует объектную модель в C, а затем оборачивает ее, чтобы она выглядела симпатично в C ++. Объектная модель C ++ по умолчанию была лишь одним из возможных подходов, и программы на C реализуют множество альтернатив.

Вы также можете сделать шаг назад и заменить массив кортежем. Кортежи могут хранить неоднородные типы, и с помощью bkt работы вы можете перебирать их:

auto shapes = make_IShapePtr_tuple(&square, &rect);

foreach_elem( shapes,[&](IShape* shape )
{
for (int side = 0 ; side < shape->countSides() ; ++side )
{
std::cout << shape->getName() << " side=" << shape->sideLength(side) << "\n";
}
});

где лямбда получает нетипизированный тип.

Ничто из этого не требует концепций:

auto shapes = std::make_tuple(&square, &rect);

foreach_elem( shapes,[&](auto* shape )
{
for (int side = 0 ; side < shape->countSides() ; ++side )
{
std::cout << shape->getName() << " side=" << shape->sideLength(side) << "\n";
}
});

выше можно написать в .

foreach_elem похоже:

template<class T, class F>
void foreach_elem( T&& t, F&& f ) {
std::apply( [&](auto&&...args){
( (void)f(decltype(args)(args)), ... );
}, std::forward<T>(t) );
}

в вместо этого строка в лямбде:

    using discard=int[];
(void)discard{ 0,((void)f(decltype(args)(args)),0)... };

который немного более тупой, и требует реализации std::apply,

В Вы должны написать структуру за пределами, которая имитирует лямбда.

4

Другие решения

Я вижу, что вы пытаетесь сделать, но это не имеет смысла для вашего варианта использования. Концепции — это способы реализации интерфейса во время компиляции, обычно для шаблонных функций. Здесь вам нужен абстрактный интерфейс — базовый класс с несколькими чисто виртуальными функциями-членами.

template <ShapeConcept S, ShapeConcept U>
bool collide(S s, U u)
{
// concrete types of S and U are known here
// can use other methods too, and enforce other concepts on the types
}

Абстрактный интерфейс реализует интерфейс во время выполнения — вы не знаете напрямую, каков конкретный тип, но вы можете работать с предоставленными методами.

bool collide(ShapeInterface& s, ShapeInterface& u)
{
// concrete types of S and U are unknown
// only methods of interfaces are available
}

На заметку, может быть, это был просто надуманный пример, но квадрат, безусловно, не Прямоугольник в объектно-ориентированном смысле. Один простой пример: кто-то может включить метод stretch на базовом классе прямоугольника, и вы должны реализовать его в своем квадрате. Конечно, как только вы растягиваете квадрат в любом измерении, он больше не квадрат. Быть осторожен.

0

Якк ответ правильный, но я чувствую, что это слишком сложно.
Ваши требования неверны в том смысле, что вы пытаетесь получить «бесплатно» то, что вы не можете получить бесплатно:

Я пытаюсь выяснить, могу ли я использовать концепции как своего рода интерфейс для классов, не требуя дополнительной нагрузки на виртуальную таблицу.

Ответ — нет. И это не потому, что накладные расходы на виртуальную таблицу являются излишними затратами.
Если вы хотите иметь массив Shapes для их использования, вам нужно хранить информацию о конкретных экземплярах.
Виртуальная машина делает это за вас (самый простой способ думать об этом — это скрытый член enum для каждого экземпляра, который сообщает компилятору во время выполнения, какие функции-члены вызывать), и если вы хотите, вы можете сделать это вручную, но вы должны сделать это как-то (например, вы могли бы использовать std::variant<Square,Rectangle>).

Если вы не сделаете этого, массив указателей на фигуры так же хорош, как и массив указателей на void. Вы не знаете, на что указывают ваши указатели.

примечание: если вы действительно боретесь с производительностью из-за виртуальных издержек, подумайте об использовании Boost polly_collection

0
По вопросам рекламы [email protected]