Отсутствие оператора typeof в С++ 03?

Мне просто интересно, как boost реализовал BOOST_TYPEOF ( в C++03), который кажется очень полезным инструментом. У кого-нибудь есть идеи?

Кроме того, я думаю, что сам С++ 03 мог бы предоставить оператор typeof, особенно когда у него уже есть sizeof(expr), который должен также знать тип expr, иначе как иначе он мог бы сообщить нам размер, не зная типа? Действительно ли возможно узнать размер, не зная типа выражения?

Если ему неизвестен тип, то компилятор сообщает нам размер чего (если не тип)? Я имею в виду, что sizeof(unknowntype) не имеет смысла для компиляторов (и людей тоже)!


person Nawaz    schedule 26.12.2010    source источник
comment
C++03 мог обеспечить многое. Но стандарт и без того был огромным, и они не стремились добавлять больше, чем было абсолютно необходимо. Так что они упустили много вещей, которые, по их собственному признанию, было бы неплохо иметь. Конечно, C++0x дает вам decltype, что решает проблему.   -  person jalf    schedule 26.12.2010
comment
@jalf: я имел в виду, что C++03 мог предоставить..., это то, что компиляторы уже реализуют sizeof(), и это невозможно, если он четко не обнаружит тип выражение. Это означает, что для предоставления оператора typeof() компиляторам не нужно делать дополнительные действия. На самом деле, typeof() поставляется бесплатно с sizeof(), так как я думаю, что последний требует больше осмотра/анализа/работы, чем первый.   -  person Nawaz    schedule 27.12.2010
comment
Нет такой вещи, как бесплатно. По крайней мере, нужно провести дополнительное тестирование. Но вы также недооцениваете сложность. Получить тип выражения достаточно просто, в простом случае, но вам нужно принять множество решений, например, сохранять ли CV-квалификаторы и ссылки. decltype имеет несколько странных правил, чтобы справиться с этим. Но, во-вторых, получить тип — самая простая часть. Затем вам нужно выяснить, как он должен взаимодействовать с другими частями языка. Вам разрешено это делать: std::vector<typeof(4)>? Это не бесплатно   -  person jalf    schedule 27.12.2010
comment
Но моя точка зрения остается в силе. Да, его могло реализовать, и это было бы полезно, но это сделало бы язык больше и сложнее, а компиляторам уже потребовалось почти десятилетие, чтобы догнать стандарт как есть. Им нужно было где-то провести черту. Они также вырезали STL, опустив около двух третей просто потому, что они не могли позволить себе сделать язык слишком большим.   -  person jalf    schedule 27.12.2010
comment
Для иллюстрации sizeof отбрасывает ссылки. Если вы вызываете sizeof для переменной типа int&, она дает вам размер int. Но должен ли typeof для той же переменной возвращать int или int&?   -  person jalf    schedule 27.12.2010
comment
@Nawaz: «мог бы иметь» - это всегда очень упрощающая и привлекающая внимание фраза, которую легко произнести впоследствии (если она предназначена для разглагольствования). Может быть, оно бы и было, если бы вы тогда были в комитете? Но технически я согласен.   -  person Sebastian Mach    schedule 15.02.2012


Ответы (5)


Он просто использует магию компилятора. Например, GCC __typeof__. Для компиляторов, которые не предоставляют такой магии, он предоставляет эмуляцию, которая может определять тип некоторых выражений, но не работает с полностью неизвестными типами.

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

template<typename> struct type2num;
template<int> struct num2type;
template<typename T> typename type2num<T>::dim &dispatch(T const&);

Затем он переходит от этого числа обратно к типу, чтобы наш 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, второе число определяет тип; в противном случае
  • первое число указывает шаблон, а второе число — аргумент шаблона первого типа.

Так что это становится

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 &dispatch_1(T const&);
template<typename T> typename type2num<T>::dim2 &dispatch_2(T const&);

#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, или вам может понадобиться более одного числа для каждого типа для записи *, & и т. д. Вы также хотите поддерживать vector< vector<int> >, поэтому вам нужно более одного числа для аргумента шаблона, заставляя build_type вызывать себя рекурсивно. Поскольку вы можете создать произвольный длинный список целых чисел, вы в любом случае можете закодировать что угодно в этой последовательности, так что только ваше творчество в том, как представить эти вещи.

В конце концов, вы, вероятно, повторно реализуете BOOST_TYPEOF :)

person Johannes Schaub - litb    schedule 26.12.2010
comment
Вы дали отличный толчок для дальнейших размышлений. Благодарность :-) - person Nawaz; 27.12.2010

Насколько я помню, boost::typeof реализован с помощью некоторых ?: хаков. Во-первых, вы начинаете с класса, который можно преобразовать в любой другой класс, например

class something {
public:
    template<typename T> operator const T&() {
        return *(T*)0;
    }
};

Правила ?: гласят, что если обе стороны имеют одинаковый тип, то результатом является этот тип. В противном случае, если один тип может быть преобразован в другой тип, это тип результата. Таким образом, делая

true ? something() : expr;

тип результата (константная ссылка на) тип expr-, но expr фактически никогда не оценивается, потому что он находится на ложной ветви. Затем вы передаете его в какое-то место, где уже есть вывод аргументов, например аргументы функции.

template<typename T> void x(const T& t) {
    // T is the type of expr.
}

Это несколько сложнее, потому что, судя по памяти, C++03 не имеет свертывания ссылок, поэтому он, вероятно, несколько более запутан, чем этот пример, скорее всего, с использованием SFINAE и признаков типа.

Как это преобразуется в фактический тип времени компиляции, который вы можете передать в шаблон, я понятия не имею. Что касается C++03, предоставляющего decltype(), то с языком C++03 есть гораздо большие проблемы, такие как ODR и порядок объявления/определения, отсутствие семантики перемещения и т. д., которые намного хуже, чем отсутствие decltype.

person Puppy    schedule 26.12.2010
comment
Кстати, можете ли вы уточнить, как ODR является проблемой с C++? Я всегда находил это естественным правилом, но, возможно, я что-то упускаю. - person Kos; 26.12.2010
comment
@DeadMG: твой последний абзац не имеет для меня смысла. Тема о typeof. Поэтому, когда компилятор уже знает размер типа выражения, очевидно, что он знает и тип. - person Nawaz; 26.12.2010
comment
@Kos: Потому что вы тратите свою жизнь на то, чтобы рассказывать компилятору то, что он уже знает, но оказывается в других единицах перевода, или его нужно объявить, а затем определить, или любую другую подобную старую чушь, которая является колоссальной тратой времени программиста. @Nawaz: Я имею в виду, что в C++, 03 или 0x уже так много wtf, что отсутствие decltype в 03 находится довольно далеко в списке того, что курил комитет? - person Puppy; 26.12.2010
comment
@DeadMG, я полагаю, вы упоминаете о необходимости повторного объявления чего-либо во многих единицах перевода (что я действительно ненавижу); как это связано с правилом одного определения? Мы говорим об одном и том же ОДР? :) - person Kos; 26.12.2010
comment
@Kos: Это не так. ODR говорит, что вы не можете определить не встроенную функцию в заголовке, поэтому, если у вас есть класс с сотней функций-членов, вы должны определить их все снова в какой-то единице перевода, заставляя вас повторять каждый аргумент, каждый возврат введите и сохраните указанное, когда оно изменится. Декларации — это еще одна WTF сама по себе. - person Puppy; 26.12.2010
comment
@DeadMG, в таком случае я бы сказал, что обе упомянутые нами проблемы подпадают под одно имя: C/C++ имеет единицы перевода вместо модулей. - person Kos; 26.12.2010
comment
@Кос: Действительно. Срок годности модуля перевода уже истек. - person Puppy; 26.12.2010

Он определяется как:

#define BOOST_TYPEOF(Expr) \
boost::type_of::decode_begin<BOOST_TYPEOF_ENCODED_VECTOR(Expr) >::type

Я не знаю подробностей, но, похоже, он ищет тип в какой-то таблице и расширяется до (запись в таблице):: тип

Нет, невозможно узнать размер, не зная типа. Просто никто не подумал добавить typeof в C++03.

person user253751    schedule 26.12.2010

Поскольку boost распространяется в виде исходного кода (а BOOST_TYPEOF в любом случае является реализацией заголовочного файла), вы, конечно, можете просто взглянуть. Он имеет много специфичных для компилятора условных компиляций, поэтому ответ заключается в том, что он специфичен для компилятора.

person Clifford    schedule 26.12.2010

Хотя это и не совсем то же самое, что typeof, в C++0x есть decltype.

person user502515    schedule 26.12.2010
comment
Чем decltype не совпадает с typeof? - person Puppy; 26.12.2010
comment
Я знаю, что C++0x имеет decltype, но здесь вопрос не в этом. вы должны написать такие вещи в комментариях. - person Nawaz; 26.12.2010