Я пытаюсь создать шаблонный класс для обеспечения корректности размеров (длина, деленная на время, дает скорость и т. Д.).
Короткая история: «Безразмерный» является одним из возможных воплощений. Было бы удобно, если бы я мог позволить все экземпляры могут быть явно созданы из двойников и, кроме того, позволяют «безразмерному» экземпляру (и только безразмерному экземпляру) быть неявно построенным из двойников.
Длинная история: Мой шаблон класса выложен как
template<int iLength, int iTime, int iMass>
class qty {
double data;
//Operators overloaded to enforce dimensional correctness
// e.g. qty<a,b,c> can only be added to qty<a,b,c>
// qty<a,b,c> * qty<a2,b2,c2> returns qty<a+a2,b+b2,c+c2>
};
Следуя этому стилю, qty<0,0,0>
является безразмерной величиной, поэтому можно добавить или вычесть qty<0,0,0>
и двойной. Я в настоящее время принудил это декларирование
qty operator+ (const double& rhs) const;
…но только определяющий это для qty<0,0,0>
, Это выполнимо … но я думаю, что мог бы сделать лучше. Если бы я позволил неявное преобразование из двойного в qty<0,0,0>
затем сложение двойного и qty<0,0,0>
не требует специальной обработки. Пользовательские ошибки также могут давать более многообещающие сообщения об ошибках — попытка добавить удвоение к скорости будет означать, что преобразование невозможно (исходя из основной идеи несовместимости измерений), а не будет жаловаться, что функция не определена ( что может заставить пользователей заподозрить ошибку в классе шаблона).
Проблема в том, что я не могу разрешить неявную конструкцию для любой другой комбинации параметров шаблона. Если бы я это сделал, то добавление любого кол-во и двойного всегда будет успешным; Я хочу заставить пользователя задуматься о корректности размеров и явно преобразовать двойные константы в соответствующее измерение перед добавлением (если это предполагаемая операция). Я, однако, хочу разрешить явный конструкция из двойников — без нее простая декларация
qty<1,-1,0> a(1.5); //speed with value 1.5
понадобится неуклюжая функция преобразования
qty<1,-1,0> a = makeQty<1,-1,0>( 1.5 ); //my eyes hurt
Это означает, что я действительно хочу
template<int iLength, int iTime, int iMass>
class qty {
double data;
explicit qty(const double& rhs) : data(rhs) {} //general version prohibits
//implicit conversion
//...
};
template<>
qty<0,0,0>::qty(const double&rhs) : data(rhs) {} //Explicit instantiation
//for dimensionless case
// ... with black magic to reallow implicit conversion
// for this instantiation only ???
Как видите, я не уверен, возможно ли удалить explicit
спецификация только для одного экземпляра, и — если это возможно — я не уверен, каков синтаксис.
Мы создаем тип, который T
или тип, который вы не можете создать в зависимости от bool:
template<bool b, typename T>
struct block_unless {
struct type { type() = delete; operator T(); }; // operator T is standard-paranoia
};
template<typename T>
struct block_unless<true, T> {
using type = T;
};
template<bool b, typename T>
using block_unless_t = typename block_unless<b,T>::type;
template<bool b, typename T>
using block_if_t = block_unless_t<!b, T>;
Затем мы защищаем методы, которые мы хотим заблокировать / активировать, встроенными в остальную часть кода:
template<int a, int b, int c>
struct qty {
enum { scalar = (a==0)&&(b==0)&&(c==0) };
explict qty( block_if_t< scalar, double > d );
qty( block_unless_t< scalar, double > d );
};
как насчет этого?
В C ++ 1y предложения require, вероятно, будут работать лучше.
(Стандартная паранойя возникает из-за недосказанности в стандарте, когда шаблонный метод должен иметь хотя бы одну действительную реализацию: пока он недоступен, operator T
означает, что ваш код работает с d
будет работать в 99% случаев, когда код ожидает double
.)
Вы не можете изменить это напрямую, но следующее будет работать с C ++ 11:
template<int iLength, int iTime, int iMass>
class qty_impl {
double data;
public:
explicit qty_impl(const double& rhs) : data(rhs) {} //general version prohibits
//implicit conversion
//...
};
// general case: simply forward to _impl
template<int iLength, int iTime, int iMass>
class qty : public qty_impl<iLength,iTime,iMass> {
// inherit ctors, including "explicit"using qty_impl<iLength,iTime,iMass>::qty_impl;
};
// special case
template<>
class qty<0,0,0> : public qty_impl<0,0,0> {
using qty_impl<0,0,0>::qty_impl;
public:
// provide non-explicit ctor to override the inherited base ctors
qty(const double& rhs) : qty_impl<0,0,0>(rhs) {}
};
Это позволяет вам поддерживать общую реализацию практически для всего и просто пересылать неявный ctor в явный.