Дополнительная возможность структурной типизации в C++ или любом другом языке?
В C++, как сказать компилятору, что Ogre::Vector3 IS_SAME_AS SomeOtherLIB::Vector3? Я чувствую это... в таких языках, как с ++, которые не являются структурно типизированными, но в некоторых случаях это имеет смысл.
Обычно в качестве разработчика игр при работе с 4+ библиотеками, которые предоставляют сортировку или собственную реализацию Vector3. Код замусорен функциями преобразования ToOgre, ToThis, ToThat. Это большое копирование Float3, которое не должно происходить на первом месте.
Находится в C++ или любых других языках, где нам не нужно преобразовывать (копировать) один тип в другой, что по сути одно и то же. Но любое решение в C++, как и большинство хороших библиотек game dev, предназначено для c/ C++.
4 ответа
Если вы используете шаблоны, вы можете определить функции, которые принимают аргументы любого типа, если для этого типа определены необходимые операции. Пример:
class Foo { void quack() {} };
class Bar { void quack() {} };
class Baz {};
template<typename Duck>
void f(Duck d) {
d.quack();
}
int main() {
f(Foo()); // works
f(Bar()); // works
f(Baz()); // compile error because Baz does not have a quack method
return 0;
}
Хотя это не подходит ни для какой ситуации, шаблоны могут дать вам "типизированную утку во время компиляции".
Допустим, у вас есть два типа вектора:
struct Vec3A {
float x, y, z;
};
struct Vec3B {
float p[3];
};
Вы можете определить шаблоны функций, которые скрывают реализацию получения компонентов:
template<class T> float get_x(const T&);
template<class T> float get_y(const T&);
template<class T> float get_z(const T&);
template<> float get_x<Vec3A>(const Vec3A& v) { return v.x; }
// ...
template<> float get_x<Vec3B>(const Vec3B& v) { return v.p[0]; }
// ...
С такими помощниками вы можете теперь написать универсальные функции, которые работают на обоих:
template<class T> float length(const T& t) {
return std::sqrt(std::pow(get_x(t), 2),
std::pow(get_y(t), 2),
std::pow(get_z(t), 2));
}
Вы также можете продолжить, специализируясь на утилитах, таких как length()
для производительности или по другим причинам, например, если определенный вектор уже имеет функцию-член, предоставляющую вам длину:
template<> float length<Vec3C>(const Vec3C& v) {
return v.length();
}
Если вы действительно уверены в случае не виртуальных структур, вы можете сделать reinterpret_cast. Однако лучше:
- выполнять шаблонные функции-оболочки, как показано в sepp2k
- наследовать от одного из векторов и добавить оператор преобразования в другой вектор
- добавить отдельную функцию _cast, которая выполняет преобразование
Haxe - очень переносимый язык с полностью необязательным структурным подтипом:
typedef Vector3 = { x : double, y : double, z : double };
class FancyVector3 {
public var x : double, y : double, z : double;
function dot(Vector3 v) {
return x * v.x + y * v.y + z * v.z;
}
function length() {
return Math.sqrt(dot(this));
}
}
Vector3 не только является уже используемой структурой, но также выступает в качестве структурного интерфейса для других классов. такие typedef
В структурах можно указывать сигнатуры функций, а также поля.
У Haxe также есть CFFI для общения с C++ (хотя он все еще требует методов преобразования), и привязки уже существуют для нескольких игровых движков C++, а также для различных низкоуровневых сред. Также разрабатываются кроссплатформенные движки, написанные на чистом Haxe, предназначенные для C++, Flash и JS (как Canvas, так и WebGL).
Возможно, это не то решение, которое вы ищете прямо сейчас, но может стать интереснее через несколько лет.