Отказ от ответственности: вопрос полностью отличается от Наследование вместо typedef , и я пока не смог найти ни одного подобного вопроса
Мне нравится играть с мета-программированием на шаблоне c ++ (в основном дома, я иногда слегка представляю его на работе, но я не хочу, чтобы программа стала доступной для чтения только тем, кто не удосужился узнать об этом), однако Я был совершенно потрясен ошибками компилятора, когда что-то пошло не так.
Проблема в том, что, конечно, метапрограммирование шаблонов c ++ основано на шаблонах, и поэтому каждый раз, когда вы получаете ошибку компилятора в глубоко вложенной структуре шаблона, вы должны копаться в сообщении об ошибке из 10 строк. Я даже взял привычку копировать / вставлять сообщение в текстовом редакторе, а затем делать отступы для сообщения, чтобы получить некоторую структуру, пока не получу представление о том, что на самом деле происходит, что добавляет некоторую работу к отслеживанию самой ошибки.
Насколько я знаю, проблема в основном из-за компилятора и того, как он выводит typedefs (есть и другие проблемы, такие как глубина вложения, но в действительности это не ошибка компилятора). Для грядущего C ++ 0x анонсированы классные функции, такие как вариационные шаблоны или вычитание типов (авто), но мне бы очень хотелось иметь более качественные сообщения об ошибках для загрузки. Использование шаблонного метапрограммирования может оказаться болезненным, и мне непонятно, чем это станет, когда в него действительно войдет больше людей.
Я заменил некоторые определения типов в своем коде и вместо этого использую наследование.
typedef partition<AnyType> MyArg;
struct MyArg2: partition<AnyType> {};
Это не намного больше символов для ввода, и это не менее читабельно, по моему мнению. На самом деле он может быть даже более читабельным, поскольку он гарантирует, что объявленный новый тип будет отображаться близко к левому полю, а не с неопределенным смещением вправо.
Это, однако, связано с другой проблемой. Чтобы убедиться, что я не делал ничего глупого, я часто писал свои функции / классы шаблонов так:
template <class T> T& get(partition<T>&);
Таким образом, я был уверен, что его можно вызвать только для подходящего объекта.
Особенно при перегрузке операторов, таких как operator +, вам нужен какой-то способ сузить область действия ваших операторов или риск, что это вызвано, например, для int.
Однако, если это работает с типом, определенным как typedef, так как это только псевдоним. Это точно не работает с наследованием ...
Для функций можно просто использовать CRTP
template <class Derived, class T> partition;
template <class Derived, class T> T& get(partition<Derived,T>&);
Это позволяет узнать «реальный» тип, который использовался для вызова метода до того, как компилятор использовал публичное наследование. Следует отметить, что это уменьшает шансы, что эта конкретная функция должна быть вызвана, поскольку компилятор должен выполнить преобразование, но я до сих пор не заметил никаких проблем.
Еще одним решением этой проблемы является добавление свойства 'tag' к моим типам, чтобы отличать их друг от друга, а затем рассчитывать на SFINAE .
struct partition_tag {};
template <class T> struct partition { typedef partition_tag tag; ... };
template <class T>
typename boost::enable_if<
boost::same_type<
typename T::tag,
partition_tag
>,
T&
>::type
get(T&)
{
...
}
Это требует еще некоторого набора текста, особенно если кто-то объявляет и определяет функцию / метод в разных местах (и если я не беспокоюсь, мой интерфейс довольно скоро перемешивается). Однако, когда дело доходит до классов, поскольку преобразование типов не выполняется, оно становится более сложным:
template <class T>
class MyClass { /* stuff */ };
// Use of boost::enable_if
template <class T, class Enable = void>
class MyClass { /* empty */ };
template <class T>
class MyClass <
T,
boost::enable_if<
boost::same_type<
typename T::tag,
partition_tag
>
>
>
{
/* useful stuff here */
};
// OR use of the static assert
template <class T>
class MyClass
{
BOOST_STATIC_ASSERT((/*this comparison of tags...*/));
};
Я склонен чаще использовать «static assert», чем «enable_if», я думаю, что он станет намного более читабельным, когда я вернусь через некоторое время.
Ну, в общем, я еще не решил, и я все еще экспериментирую между различными технологиями, выставленными здесь.
Используете ли вы typedefs или наследование?
Как вы ограничиваете область действия ваших методов / функций или иным образом контролируете тип аргументов, предоставляемых им (и для классов)?
И, конечно, я бы хотел, чтобы эти личные предпочтения были больше, если это возможно. Если есть веская причина для использования определенной техники, я бы лучше знал об этом!
EDIT:
Я просматривал stackoverflow и только что нашел этот perl из Boost.MPL, который я полностью забыл:
BOOST_MPL_ASSERT_MSG
Идея в том, что вы дадите макросу 3 аргумента:
- Условие проверить
- сообщение (идентификатор C ++), которое следует использовать для отображения в сообщении об ошибке
- список задействованных типов (в виде кортежа)
Это может значительно помочь как в документировании кода, так и в улучшении вывода ошибок.