#c #templates #boost #metaprogramming #typeof
#c #шаблоны #повышение #метапрограммирование #typeof
Вопрос:
Мне просто интересно, как boost реализовал BOOST_TYPEOF (в C 03), который кажется очень полезным инструментом. У кого-нибудь есть какие-нибудь идеи?
Кроме того, я думаю, что C 03 сам мог бы предоставить typeof
operator , особенно когда у него уже есть, sizeof(expr)
который также должен знать тип expr
, иначе как еще он мог бы сообщить нам размер, не зная типа?Действительно ли возможно узнать размер, не зная типа выражения?
Если он не знает тип, то компилятор сообщает нам размер чего (если не типа)? Я имею в виду, sizeof(unknowntype)
это не имело бы смысла для компиляторов (и людей в том числе)!
Комментарии:
1. C 03 мог бы многое предоставить. Но стандарт и так был огромным , и они не стремились добавлять больше, чем было абсолютно необходимо. Таким образом, они упустили много вещей, которые, по их собственному признанию, было бы неплохо иметь. Конечно, C 0x дает вам
decltype
, который решает проблему.2. @jalf : Под «C 03 мог бы предоставить …» я имел в виду то, что компиляторы уже реализуют
sizeof()
, а это невозможно, если он четко не определяет тип выражения. Это означает, что для предоставленияtypeof()
оператора компиляторам не нужно делать дополнительных действий. Фактически,typeof()
поставляется бесплатно сsizeof()
, поскольку я думаю, что последнее требует больше проверки / анализа / работы, чем первое.3. Такого понятия, как «свободно», не существует. По крайней мере, необходимо провести дополнительное тестирование. Но вы также недооцениваете сложность. Получить тип выражения достаточно просто в простом случае , но вам нужно принять множество решений о том, сохранять ли CV-квалификаторы и ссылки, например.
decltype
есть несколько странных правил, чтобы справиться с этим. Но, во-вторых, получение типа — это простая часть. Затем вам нужно выяснить, как он должен взаимодействовать с другими частями языка. Должно ли вам быть разрешено делать это:std::vector<typeof(4)>
? Это не предоставляется «бесплатно»4. Но моя точка зрения все еще остается в силе. Да, это можно было бы реализовать, и это было бы полезно, но это сделало бы язык больше и сложнее, а компиляторам уже потребовалось почти десятилетие, чтобы догнать существующий стандарт. Они должны были где-то провести черту. Они также вырезали STL, исключив что-то вроде двух третей из него, просто потому, что не могли позволить себе сделать язык слишком большим.
5. Просто для иллюстрации,
sizeof
отбрасывает ссылки. Если вы вызываетеsizeof
переменную типаintamp;
, это дает вам размер int. Но следует лиtypeof
возвращать ту же переменнуюint
илиintamp;
?
Ответ №1:
Он просто использует магию компилятора. Например, GCC __typeof__
. Для компиляторов, которые не предоставляют такого волшебства, он предоставляет эмуляцию, которая может определять тип некоторых выражений, но завершается неудачей с совершенно неизвестными типами.
Возможной реализацией могло бы быть наличие списка функций, которые принимают выражение заданного типа, а затем отправляют из этого типа в число с использованием шаблона класса. Чтобы шаблон функции возвращал число как объект времени компиляции, мы помещаем его в измерение массива
template<typename> struct type2num;
template<int> struct num2type;
template<typename T> typename type2num<T>::dim amp;dispatch(T constamp;);
Затем он переходит от этого числа обратно к типу, чтобы наш EMUL_TYPEOF
мог напрямую назвать тип. Итак, чтобы зарегистрировать тип, мы пишем
#define REGISTER_TYPE(T, N)
template<>
struct type2num<T> {
static int const value = N;
typedef char dim[N];
};
template<>
struct num2type<N> { typedef T type; }
Имея это на месте, вы можете написать
#define EMUL_TYPEOF(E)
num2type<sizeof dispatch(E)>::type
Всякий раз, когда вам нужно зарегистрировать тип, вы пишете
REGISTER_TYPE(int, 1);
REGISTER_TYPE(unsigned int, 2);
// ...
Конечно, теперь вы обнаруживаете, что вам нужен механизм для принятия, vector<T>
когда вы не знаете T
заранее, и тогда он становится произвольно сложным. Вы могли бы создать систему, в которой числа означают больше, чем просто тип. Вероятно, это могло бы сработать:
#define EMUL_TYPEOF(E)
build_type<sizeof dispatch_1(E), sizeof dispatch_2(E)>::type
Это может определять типы, подобные int
, а также типы, подобные shared_ptr<int>
— другими словами, типы, которые не являются специализациями шаблона класса, и специализации шаблона класса с одним аргументом шаблона, путем выполнения некоторого систематического сопоставления
- Если первое число дает 1, второе число указывает тип; в противном случае
- первое число указывает шаблон, а второе число — его первый аргумент type template
Таким образом, это становится
template<int N, int M>
struct build_type {
typedef typename num2tmp<N>::template apply<
typename num2type<M>::type>::type type;
};
template<int N>
struct build_type<1, N> {
typedef num2type<N>::type type;
};
Нам также нужно изменить dispatch
шаблон и разделить его на две версии, показанные ниже, вместе с REGISTER_TEMP1
для регистрации шаблонов с одним аргументом
template<typename T> typename type2num<T>::dim1 amp;dispatch_1(T constamp;);
template<typename T> typename type2num<T>::dim2 amp;dispatch_2(T constamp;);
#define REGISTER_TYPE(T, N)
template<>
struct type2num<T> {
static int const value_dim1 = 1;
static int const value_dim2 = N;
typedef char dim1[value_dim1];
typedef char dim2[value_dim2];
};
template<>
struct num2type<N> { typedef T type; }
#define REGISTER_TMP1(Te, N)
template<typename T1>
struct type2num< Te<T1> > {
static int const value_dim1 = N;
static int const value_dim2 = type2num<T1>::value_dim2;
typedef char dim1[value_dim1];
typedef char dim2[value_dim2];
};
template<> struct num2tmp<N> {
template<typename T1> struct apply {
typedef Te<T1> type;
};
}
Регистрация std::vector
шаблона и оба int
варианта теперь выглядят следующим образом
REGISTER_TMP1(std::vector, 2);
// ... REGISTER_TMP1(std::list, 3);
REGISTER_TYPE(int, 1);
REGISTER_TYPE(unsigned int, 2);
// ... REGISTER_TYPE(char, 3);
Вероятно, вы также хотите зарегистрировать несколько чисел для каждого типа, по одному числу для каждой комбинации const / volatile или может потребоваться более одного числа для каждого типа для записи *
, amp;
и тому подобное. Вы также хотите поддерживать vector< vector<int> >
, поэтому вам также нужно более одного числа для аргумента шаблона, что делает build_type
сам вызов рекурсивным. Поскольку вы можете создать произвольный длинный список целых чисел, вы все равно можете закодировать что угодно в эту последовательность, так что только от вашего творчества зависит, как представить эти вещи.
В конце концов, вы, вероятно, переопределяете BOOST_TYPEOF 🙂
Комментарии:
1. Вы дали отличный толчок для дальнейших размышлений. спасибо 🙂
Ответ №2:
Из памяти boost::typeof реализован с помощью некоторых ?: взломов. Сначала вы начинаете с класса, который может быть преобразован в любой другой класс, например
class something {
public:
template<typename T> operator const Tamp;() {
return *(T*)0;
}
};
В ?: правилах указано, что если обе стороны имеют одинаковый тип, то результатом будет этот тип. В противном случае, если один тип может быть преобразован в другой тип, это тип результата. Итак, выполнив
true ? something() : expr;
тип результата — это (постоянная ссылка на) тип expr — но expr на самом деле никогда не вычисляется, потому что он находится в ветви false. Итак, затем вы передаете его в какое-то место, в котором уже есть вывод аргументов, например, аргументов функции.
template<typename T> void x(const Tamp; t) {
// T is the type of expr.
}
Это несколько сложнее, потому что из памяти C 03 не имеет свертывания ссылок, поэтому, вероятно, он несколько более запутанный, чем этот пример, — скорее всего, с использованием SFINAE и признаков типа.
Я понятия не имею, как это преобразуется в фактический тип во время компиляции, который вы можете передать в шаблон. Что касается C 03, предоставляющего decltype(), ну, с языком C 03 есть гораздо большие проблемы, такие как ODR и порядок объявления / определения, отсутствие семантики перемещения и т.д., Которые намного хуже, чем отсутствие decltype.
Комментарии:
1. Кстати — можете ли вы подробнее рассказать о том, как ODR является проблемой в C ? Я всегда считал это естественным правилом, но, возможно, я что-то упускаю.
2. @DeadMG: ваш последний абзац не имеет смысла для меня. Тема о
typeof
. Итак, когда компилятор уже знает размер типа выражения, то очевидно, что он также знает тип .3. @Kos: Потому что вы тратите свою жизнь на то, чтобы рассказывать компилятору то, что он уже знает, но случайно находится в других единицах перевода, или должно быть объявлено, а затем определено, или любую другую подобную чушь, которая является колоссальной тратой времени программиста. @Nawaz: Я хочу сказать, что в C , 03 или 0x, уже так много wtf, что отсутствие decltype в 03 занимает довольно низкое место в списке «Что курил комитет?».
4. @DeadMG, я полагаю, вы упоминаете необходимость повторного объявления чего-либо во многих единицах перевода (что я действительно ненавижу); как это связано с одним правилом определения? Мы говорим об одном и том же ODR? 🙂
5. @DeadMG, в таком случае я бы сказал, что обе проблемы, о которых мы упоминаем, подпадают под одно название: «C / C имеют единицы перевода вместо модулей».
Ответ №3:
Это определяется как:
#define BOOST_TYPEOF(Expr)
boost::type_of::decode_begin<BOOST_TYPEOF_ENCODED_VECTOR(Expr) >::type
Я не знаю подробностей, но, похоже, он ищет тип в какой-то таблице и расширяется до (запись в таблице)::type
Нет, невозможно узнать размер, не зная типа. Просто никто не додумался добавить typeof в C 03.
Ответ №4:
Поскольку boost распространяется в виде исходного кода (а BOOST_TYPEOF available в любом случае является реализацией файла заголовка), вы, конечно, могли бы просто взглянуть. В нем много специфичной для компилятора условной компиляции, поэтому ответ заключается в том, что он зависит от компилятора.
Ответ №5:
Хотя и не совсем то же самое, что typeof
в C 0x decltype
.
Комментарии:
1. Почему decltype не совпадает с typeof?
2. Я знаю, что C 0x имеет decltype, но здесь вопрос не в этом. вы должны написать такую вещь в комментариях.