Свежие новости с C++0x фронта - разбор полётов
От: Masterkent  
Дата: 27.03.10 18:35
Оценка: 32 (6) :))
VladD2:

VD>Меня же во всем в этом веселит тот факт, что набор крайне примитивных изменений не могут принять вот уже 12 лет. За это время уже целое поколение новых языков выросло, а комитет все встречается и встречается...


Насчёт примитивности изменений не соглашусь. Всё-таки составить вменяемые и согласованные друг с другом правила не так-то просто. Однако результативность работы комитета по стандартизации C++ меня сильно удивляет. Чтобы сделать столько оплошностей и годами их не замечать, это нужно иметь талант. Проводить максимально полный разбор полётов я, конечно, не буду (на это бы ушло очень много времени), но несколько примечательных примеров приведу:

  Скрытый текст
1) Ограничения на использование выражений типа void.

N3035 — 3.9.1/9:

Any expression can be explicitly converted to type cv void (5.4). An expression of type void shall be used only as an expression statement (6.2), as an operand of a comma expression (5.18), as a second or third operand of ?: (5.16), as the operand of typeid, or as the expression in a return statement (6.6.3) for a function with the return type void.

Эти два соседние предложения противоречат друг другу: из первого следует, что выражение типа void можно преобразовать к типу void, а из второго следует, что такое преобразование недопустимо (т.к. не подпадает ни под один из пяти вариантов легального использования выражений типа void). Забавно, не правда ли? Но это ещё не всё. Должно ли вот такое дело

template <class Fn, class... Types>
    auto fwd(Fn fn, Types &&... args) ->
        decltype(fn(std::forward<Types>(args)...))
{
    return fn(std::forward<Types>(args)...);
}

работать, если тип выражения fn(std::forward<Types>(args)...) — это void? Фигушки. Применение decltype к выражениям типа void незаконно (такое использование выражений типа void не значится в списке легальных — см. выше). Сильно сомневаюсь, что на это ограничение есть какие-то веские причины — скорее всего, про данный случай просто забыли.

2) Неявное добавление "(*this)."

Допустим, у нас есть структура S c non-static data member-ом m и мы хотим узнать размер S::m. Хорошая новость: теперь вместо зубодробительной конструкции sizeof ((S*)0)->m мы сможем использовать просто sizeof S::m.

N3035 — 5.1.1/10:

An id-expression that denotes a non-static data member or non-static member function of a class can only be used
[...]
— if that id-expression denotes a non-static data member and it appears in an unevaluated operand.
[ Example:

struct S {
  int m;
};
int i = sizeof(S::m);      // OK
int j = sizeof(S::m + 42); // OK

Плохая новость: причудливые правила не разрешают использование таких конструкций в нестатических функциях-членах некоторых классов:

struct B
{
    int m;
};

struct D : B
{
    void f()
    {
        int size = sizeof B::m; // OK
    }
};

struct X
{
    static void f()
    {
        int size = sizeof B::m; // OK
    }
    void g()
    {
        int size = sizeof B::m; // ill-formed
    }
};

Всё дело в том, что по новым правилам любой non-static member в любой non-static member function (независимо от того, к какому классу принадлежит эта функция) должен неявно предваряться конструкцией "(*this).".

N3035 — 9.3.1/3:

When an id-expression (5.1) that is not part of a class member access syntax (5.2.5) and not used to form a pointer to member (5.3.1) is used in the body of a non-static member function of class X, if name lookup (3.4.1) resolves the name in the id-expression to a non-static non-type member of some class C, the id-expression is transformed into a class member access expression (5.2.5) using (*this) (9.3.2) as the postfix-expression to the left of the . operator. [ Note: if C is not X or a base class of X, the class member access expression is ill-formed. —end note ]

Т.е. в нашем случае выражение B::m внутри функции X::g неявно трансформируется в (*this).B::m. Естественно, добавление "(*this)." к совершенно чужому member-у незаконно. Странное дело получается: мы, вроде бы, "(*this)." не пишем, но оно само собой приписывается и делает нашу программу некорректной.

Возникает естественный вопрос: откуда взялись такие правила? Как оказалось, это заслуга автора, который захотел по-быстрому решить проблему, описанную в DR 515. Но что сподвигло его на столь необычное разрешение проблемы (добавлять (*this). к чему ни попадя), остаётся для меня загадкой.

3) Целые статические константы, объявленные с инициализатором в определении класса, всё ещё нуждаются в определении вне класса при их использовании.

В следующем примере

struct X
{
    static int const value = 1;
};

template <class T>
    void f(T const &) { /*....*/ }
    
int main()
{
    f(X::value);
}

попытка формирования ссылки на неопределённый объект может породить ошибку трансляции. По правилам C++03 даже такая программа некорректна:

struct X
{
    static int const value = 1;
};

void f(int) { /*....*/ }
    
int main()
{
    f(X::value);
}

Непременно нужно внешнее определение, а то ж бедный транслятор программы ни за что не догадается, что кто-то использует адрес переменной и под неё-таки нужно выделить маленький клочок памяти (а в случаях, когда адрес не используется, жадно сэкономить несколько байтиков).

Вместо того чтобы раз и навсегда избавить программиста от лишней писанины во всех подобных случаях (такие переменные можно было бы считать определёнными независимо от наличия внешнего определения), комитет решил ограничиться лишь сокращением случаев, относящихся к использованию объектов (требующих присутствие определения).

4) Использование длинных синтаксических конструкций.

Шаблоны в <type_traits> — это какое-то убожество. Если в Boost ещё хоть как-то боролись с синтаксическими нагромождениями (например, см. boost::enable_if vs boost::enable_if_c), то здесь же всюду нужно дописывать ::value и ::type (в последнем случае ещё и typename приходится использовать, если есть зависимость от параметров шаблона). Что, к примеру, мешает определить is_pointer и enable_if следующим образом?

template <class T>
    struct is_pointer_impl : false_type {};
template <class T>
    struct is_pointer_impl<T *> : true_type {};
template <class T>
    struct is_pointer_impl<T *const> : true_type {};
template <class T>
    struct is_pointer_impl<T *volatile> : true_type {};
template <class T>
    struct is_pointer_impl<T *const volatile> : true_type {};
template <class T>
    constexpr bool is_pointer()
        { return is_pointer_impl<T>::value; }

template <bool b, class T = void>
    struct enable_if_impl;
template <class T>
    struct enable_if_impl<true, T>
        { typedef T type; };
template <bool b, class T = void>
    using enable_if = typename enable_if_impl<b, T>::type;

Мы могли бы использовать эти шаблоны примерно так:

template <class T>
    enable_if<is_pointer<T>()> f(T);

А вот так выглядит аналогичное использование стандартных шаблонов в их нынешнем виде:

template <class T>
    typename enable_if<is_pointer<T>::value>::type f(T);

Как видно, комитет заметно усовершенствовал ядро языка, но довести до ума стандартную библиотеку пороху не хватило.



В общем, пристально изучая черновик, в нём можно заметить очень много недостатков. Если бы правила C++ верстались на коленке парой человек в течение пары лет, то такой расклад не казался бы странным. Но, поскольку над стандартом работает международный комитет по стандартизации, да ещё и на протяжении многих лет, вся эта халтура выглядит как-то несерьёзно.

Остаётся лишь добавить, что чем раньше выйдет новый стандарт, тем больше в нём останется незалатанных дыр, так что в скором выходе стандарта есть свои минусы.

30.03.10 18:29: Ветка выделена из темы Свежие новости с C++0x фронта
Автор: alexeiz
Дата: 14.03.10
— Кодт
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.