2012-07-01 5 views
2

Я реализовал свой собственный класс единиц СИ. При использовании арифметических операций полученный SI-блок может измениться. Например: (метр/секунда)/метр = 1/сек.C++ шаблон возвращаемого типа в зависимости от аргументов шаблона?

Теперь я также создал простой класс 3D Vector. Этот вектор должен быть общим, а также использоваться с моим классом единиц СИ. Поэтому я реализовал простой оператор разделения. Смотрите следующий код:

// Determine result type of Lhs/Rhs: 
template < class Lhs, class Rhs > 
    struct TV3TypeV3Div { typedef BOOST_TYPEOF(Lhs()/Rhs()) type; }; 

// Vector/Vector: 
template < class Lhs, class Rhs > 
RobotTools::DataTypes::TV3Type< typename TV3TypeV3Div< Lhs, Rhs >::type > operator/(const RobotTools::DataTypes::TV3Type<Lhs>& lhs, 
                        const RobotTools::DataTypes::TV3Type<Rhs>& rhs) 
{ 
    // do something useful 
    return RobotTools::DataTypes::TV3Type< typename TV3TypeV3Div< Lhs, Rhs >::type >(0, 0, 0); 
} 

// Vector/Vector 
RobotTools::DataTypes::TV3Type<Tools::DataTypes::Length> vl; 
vl/vl; // Ok this works 

во время компиляции правильного типа возврата будет определяться с помощью TV3TypeV3Div-структуру. Это работает.

Теперь я хочу расширить операторы. Я также хочу рассчитать векторы со скалярными типами. Поэтому я написал этого оператора:

// Vector/Scalar 
template < class Lhs, class Rhs > 
RobotTools::DataTypes::TV3Type< typename TV3TypeV3Div< Lhs, Rhs >::type > operator/(const RobotTools::DataTypes::TV3Type<Lhs>& lhs, 
                        const Rhs& rhs) 
{ 
    // do something useful 
    return RobotTools::DataTypes::TV3Type< typename TV3TypeV3Div< Lhs, Tools::DataTypes::Length >::type >(0, 0, 0); 
} 

// Vector/Scalar 
RobotTools::DataTypes::TV3Type<Tools::DataTypes::Length> vl; 
Tools::DataTypes::Length sl; 
vl/sl; // Ok nice it works too 

Пока все хорошо. Проблема в том, что когда я определяю второй оператор (Vector/Scalar), этот оператор настолько обобщен, что компилятор хочет использовать его также для векторного/векторного деления. Но это не удается, потому что LHS()/Rhs() с:

LHS = Инструменты :: Datatypes :: Длина и RHS = RobotTools :: Datatypes :: TV3Type

не определен. Это правильно, и я понимаю данную ошибку. Я не понимаю, что компилятор НЕ использует оператор Vector/Vector.

  • Есть ли возможность дать компилятору подсказку, какой оператор использовать?
  • Есть ли возможность переписать операторов для полного заполнения моего требования?

ответ

2

Компилятор не хочет использовать оператор Vector/Scalar для векторного/векторного деления. Он просто хочет проверить, есть ли матч.

Если объявить (но не определить) вполне общий оператор деления

template <typename Lhs, typename Rhs> 
struct InvalidDivision {}; 
template <typename Lhs, typename Rhs> 
InvalidDivision<Lhs, Rhs> 
operator/(const Lhs& lhs, const Rhs& rhs); // do not define 

тогда ваш код должен собрать и ссылку. Перегрузка Vector/Scalar будет рассмотрена и отклонена, потому что Vector/Vector лучше соответствует.

Недостатком этого является то, что если вы разделите вещи, для которых не определено разделение, компилятор будет жаловаться на InvalidDivision<something,other> вместо обычной ошибки.

Возможно, это может быть улучшено с помощью SFINAE или какой-либо другой продвинутой магии.

Update: более подробное объяснение

Что происходит в оригинальной версии кода?

Мы пытаемся вызвать оператор деления для пользовательского типа. Есть две функции с именем operator/, и компилятор рассматривает и то и другое, и пытается выяснить, какой из них лучше.

Сперва рассмотрим вектор/вектор operator/.Компилятор выводит, что параметры шаблона Lhs и Rhs: Length (я сокращаю пространства имен для краткости). Затем он заменяет их на параметры TV3TypeV3Div, вычисляет внутренности TV3TypeV3Div, определяет, что TV3TypeV3Div<Lhs,Rhs>::type также является Length и, наконец, вычисляет тип возврата operator/, который составляет TV3Type<Length>.

Теперь рассмотрим Вектор/Скаляр operator/. Компилятор выводит, что Lhs - Length, но Rhs - TV3Type<Length>. Затем он заменяет эти типы на параметры TV3TypeV3Div и пытается вычислить внутренности TV3TypeV3Div. Вот где вещи расстраиваются: там/и нет operator/, которые приняли бы Length и TV3Type<Length>. Поэтому невозможно вычислить TV3TypeV3Div<Lhs,Rhs>::type. Компилятор выдает ошибку.

Теперь рассмотрим, что произойдет, когда объявлен общий operator/. Сейчас - a operator/, который принимал бы Length и TV3Type<Length>! (Или любые другие пара аргументов, если на то пошло). Поэтому компилятор с радостью вычисляет TV3TypeV3Div<Lhs,Rhs>::type, а затем возвращает тип Vector/Scalar operator/. Поэтому теперь можно рассматривать перегрузки как Vector/Vector, так и Vector/Scalar operator/.

Компилятор теперь также видит и рассматривает общий operator/ для разрешения перегрузки. Все три перегрузки создают совпадение. Но перегрузка Vector/Vector выигрывает, потому что она более специализирована, чем Vector/Scalar или generic operator/, и поэтому лучше подходит.

Update 2

Должна быть возможность сделать это:

namespace Detail 
{ 
    template <typename Lhs, typename Rhs> 
     struct DoNotUse {}; 
    template <typename Lhs, typename Rhs> 
     DoNotUse<Lhs, Rhs> operator/(const Lhs& lhs, const Rhs& rhs); 
    template < class Lhs, class Rhs > 
     struct TV3TypeV3Div { typedef BOOST_TYPEOF(Lhs()/Rhs()) type; }; 
} 
using Detail::TV3TypeV3Div; 

Это должно хорошо скрыть общий оператор/от всех, кроме TV3TypeV3Div.

+0

Я смущен! Это действительно работает. Я потратил целый день на это простое решение :) Не могли бы вы объяснить это немного больше. Я не вижу волшебства. – Mark

+0

Я добавил несколько объяснений. –

+0

Спасибо n.m. за великое объяснение. Тем временем я пробовал ваш подход в рамках моего первоначального проекта. Но он не работает в нескольких местах с кодом ошибки InvalidDivision .... Я думаю, будет непросто обновить весь проект. Так что, может быть, мне нужна магия SFINAE, возможно, используя boost? Есть идеи ? – Mark

Смежные вопросы