Дополнительная возможность структурной типизации в 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. Однако лучше:

  1. выполнять шаблонные функции-оболочки, как показано в sepp2k
  2. наследовать от одного из векторов и добавить оператор преобразования в другой вектор
  3. добавить отдельную функцию _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).

Возможно, это не то решение, которое вы ищете прямо сейчас, но может стать интереснее через несколько лет.

Другие вопросы по тегам