Достижение прямой совместимости с C ++ 11

12

Я работаю над большим программным приложением, которое должно работать на нескольких платформах. Некоторые из этих платформ поддерживают некоторые функции C ++ 11 (например, MSVS 2010), а некоторые не поддерживают какие-либо (например, GCC 4.3.x). Я ожидаю, что эта ситуация будет продолжаться в течение нескольких лет (мое лучшее предположение: 3-5 лет).

Учитывая это, я хотел бы настроить интерфейс совместимости таким образом, чтобы (насколько это было возможно) люди могли писать код на C ++ 11, который все равно будет компилироваться со старыми компиляторами с минимальным обслуживанием. В целом, цель состоит в том, чтобы минимизировать # ifdef настолько, насколько это возможно, при этом поддерживая базовый синтаксис / функции C ++ 11 на платформах, которые их поддерживают, и обеспечивать эмуляцию на платформах, которые этого не делают.

Давайте начнем с std :: move (). Самый очевидный способ достижения совместимости - поместить что-то вроде этого в общий заголовочный файл:

#if !defined(HAS_STD_MOVE)
namespace std { // C++11 emulation
  template <typename T> inline T& move(T& v) { return v; }
  template <typename T> inline const T& move(const T& v) { return v; }
}
#endif // !defined(HAS_STD_MOVE)

Это позволяет людям писать такие вещи, как

std::vector<Thing> x = std::move(y);

... безнаказанно. Он делает то, что они хотят в C ++ 11, и он делает все возможное, в C ++ 03. Когда мы наконец отбросим последний из компиляторов C ++ 03, этот код может остаться как есть.

Однако, согласно стандарту, вводить новые символы в stdпространство имен незаконно . Это теория. Мой вопрос: практически говоря, есть ли какой-то вред в этом как способ достижения прямой совместимости?

mcmcc
источник
1
Boost уже предоставляет довольно много этого и уже имеет код для использования новых функций, когда / где это возможно, так что вы можете просто использовать то, что предоставляет Boost, и покончить с этим. Конечно, есть ограничения - большинство новых функций были добавлены именно потому, что решения на основе библиотек не подходят.
Джерри Коффин
да, я имею в виду именно те функции, которые могут быть реализованы на уровне библиотеки, а не синтаксические изменения. Повышение на самом деле не решает проблему (бесшовной) прямой совместимости. Если я что-то упустил ...
mcmcc
В Gcc 4.3 уже есть множество возможностей C ++ 11, и ссылки на Rvalue, вероятно, являются наиболее важными.
Ян Худек
@JanHudec: Ты прав. Бедный пример. В любом случае, есть другие компиляторы, которые определенно не поддерживают синтаксис (например, какая бы версия IBM C ++ не использовалась).
mcmcc

Ответы:

9

Я долгое время работал над поддержанием уровня прямой и обратной совместимости в моих программах на C ++, пока мне, в конечном счете, не пришлось делать из этого библиотечный инструментарий , который я готовлю к выпуску , уже выпущен. В общем, до тех пор, пока вы принимаете, что вы не получите «идеальной» прямой совместимости ни в функциях (некоторые вещи просто не могут быть эмулированы в прямом направлении), ни в синтаксисе (вам, вероятно, придется использовать макросы, альтернативные пространства имен для некоторые вещи) тогда у вас все готово.

Существует множество функций, которые можно эмулировать в C ++ 03 на уровне, достаточном для практического использования, и без всяких хлопот, которые возникают, например: Boost. Черт, даже предложение по стандартам C ++ nullptrпредлагает бэкпорт C ++ 03. И еще есть TR1, например, для всего C ++ 11, но «у нас были предварительные просмотры» в течение многих лет. Мало того, некоторые функции C ++ 14, такие как варианты assert, прозрачные функторы и optional могут быть реализованы в C ++ 03!

Единственные две вещи, которые я знаю, которые не могут быть полностью перенесены, - это шаблоны constexpr и variadic.

Что касается всего вопроса добавления чего-либо в пространство имен std, я считаю, что это не имеет значения - вообще. Вспомните Boost, одну из наиболее важных и актуальных библиотек C ++, и их реализацию TR1: Boost.Tr1. Если вы хотите улучшить C ++, сделать его более совместимым с C ++ 11, то по определению вы превращаете его в нечто, не являющееся C ++ 03, поэтому блокирование себя над стандартом, которого вы намерены избегать или оставить позади, в любом случае Проще говоря, контрпродуктивно. Пуристы будут жаловаться, но по определению не нужно заботиться о них.

Конечно, то, что вы не будете следовать (03) Стандарту, вовсе не означает, что вы не можете попытаться или будете радостно его нарушать. Не в этом дело. До тех пор, пока вы очень тщательно следите за тем, что добавляется в stdпространство имен, и имеете контроль над средами, в которых используется ваше программное обеспечение (т. Е .: проводите тестирование!), Никакого необратимого вреда не должно быть вообще. Если возможно, определите все в отдельном пространстве имен и добавьте только usingдирективы в пространство имен, stdчтобы вы не добавляли туда ничего, кроме того, что «абсолютно» нужно было бы ввести. Что, IINM, более или менее то, что делает Boost.TR1.


Обновление (2013) : в качестве запроса исходного вопроса и просмотра некоторых комментариев, которые я не могу добавить из-за отсутствия повторений, приведен список функций C ++ 11 и C ++ 14 и их степень переносимости. до C ++ 03:

  • nullptr: полностью реализуемо с учетом официального бэкпорта комитета; вам, вероятно, придется также предоставить некоторые специализации type_traits, чтобы он распознавался как «нативный» тип.
  • forward_list: полностью реализуемо, хотя поддержка распределителя зависит от того, что может обеспечить ваша реализация Tr1.
  • Новые алгоритмы (partition_copy и т. Д.): Полностью реализуемы.
  • Контейнерные конструкции из последовательностей скобок (например :) vector<int> v = {1, 2, 3, 4};: полностью реализуемы, хотя и более многословны, чем хотелось бы.
  • static_assert: почти полностью реализуемо при реализации в виде макроса (вам нужно быть осторожным только с запятыми).
  • unique_ptr: почти полностью реализуем, но вам также понадобится поддержка вызова кода (для хранения их в контейнерах и т. д.); смотрите ниже, хотя.
  • rvalue-reference: почти полностью реализуемо в зависимости от того, сколько вы ожидаете получить от них (например: Boost Move).
  • Итерация по каждому элементу: почти полностью реализуема, синтаксис будет несколько отличаться.
  • использование локальных функций в качестве аргументов (например, для преобразования): почти полностью реализуемо, но синтаксис будет достаточно отличаться - например, локальные функции не определяются на месте вызова, а прямо перед этим.
  • операторы явного преобразования: реализуемые на практических уровнях (получение явного преобразования), см. « явный_каст» в Imperfect C ++ ; но интеграция с языковыми функциями, такими как, static_cast<>может быть почти невозможна.
  • переадресация аргументов: реализуется на практических уровнях, указанных выше для rvalue-ссылок, но вам нужно будет обеспечить N перегрузок для ваших функций, принимающих пересылаемые аргументы.
  • двигаться: реализуемо на практических уровнях (см. два выше). Конечно, вам придется использовать контейнеры-модификаторы и объекты, чтобы получить прибыль от этого.
  • Распределители с областью видимости: Реально не реализуемы, если ваша реализация Tr1 не может помочь.
  • Типы многобайтовых символов: Реально не реализуемы, если ваш Tr1 не может вас поддержать. Но для предполагаемой цели лучше полагаться на библиотеку, специально предназначенную для решения этой проблемы, такую ​​как ICU, даже если используется C ++ 11.
  • Variadic списки аргументов: реализуются с некоторыми хлопотами, обратите внимание на переадресацию аргументов.
  • noexcept: зависит от возможностей вашего компилятора.
  • Новая autoсемантика и decltype: зависит от особенностей вашего компилятора - например .: __typeof__.
  • целочисленные типы ( int16_tи т. д.): зависит от возможностей вашего компилятора или вы можете делегировать его в Portable stdint.h.
  • Атрибуты типа: зависит от возможностей вашего компилятора.
  • Список инициализаторов: Насколько мне известно, это невозможно реализовать; однако, если вы хотите инициализировать контейнеры с последовательностями, см. выше в разделе «конструкции контейнера».
  • Псевдоним шаблона: насколько мне известно, он не реализуем, но в любом случае это ненужная функция, и у нас ::typeвсегда были шаблоны
  • Шаблоны Variadic: Насколько мне известно, не реализуемы; закрытие - аргумент шаблона по умолчанию, который требует N специализаций и т. д.
  • constexprНасколько мне известно, это невозможно осуществить.
  • Унифицированная инициализация: Насколько мне известно, не реализуемо, но гарантированная инициализация -конструктора по умолчанию может быть реализована в стиле Boost-initialized.
  • C ++ 14 dynarray: полностью реализуемо.
  • C ++ 14 optional<>: практически полностью реализуем, если ваш компилятор C ++ 03 поддерживает настройки выравнивания.
  • Прозрачные функторы C ++ 14: почти полностью реализуемы, но вашему клиентскому коду, вероятно, придется явно использовать, например, std::less<void>чтобы заставить его работать.
  • C ++ 14 новых вариантов утверждений (таких как assure): полностью реализуемых, если вы хотите утверждений , почти полностью реализуемых, если вы хотите вместо этого включить броски.
  • Расширения кортежей C ++ 14 (получить элемент кортежа по типу): полностью реализуемы, и вы можете даже заставить его не скомпилироваться с точными случаями, описанными в предложении функции.

(Отказ от ответственности: некоторые из этих функций реализованы в моей библиотеке бэкпортов C ++, которую я связал выше, поэтому я думаю, что знаю, о чем говорю, когда говорю «полностью» или «почти полностью».)

Луис Мачука
источник
6

Это принципиально невозможно. Посмотрим std::unique_ptr<Thing>. Если бы можно было эмулировать ссылки на rvalue как библиотеку, это не было бы языковой особенностью.

DeadMG
источник
1
Я сказал "до какой бы степени". Очевидно, что некоторые функции должны быть оставлены позади # ifdef или не использоваться вообще. std :: move () может поддерживать синтаксис (хотя и не функциональность).
mcmcc
2
На самом деле в предложении rvalue упоминается решение на основе библиотеки!
Ян Худек
Более конкретно, можно реализовать семантику перемещения для определенного класса в C ++ 03, поэтому там должно быть возможно определить std::unique_ptrэто, но некоторые другие функции ссылок на rvalue не могут быть реализованы в C ++ 03, поэтому std::forwardэто невозможно. Другое дело, что std::unique_ptrони не будут полезны, потому что коллекции не будут использовать семантику перемещения, если вы не замените их все.
Ян Худек
@JanHudec: невозможно определить unique_ptr. Посмотрите на недостатки auto_ptr. unique_ptrэто практически учебный пример класса, семантика которого была в основном включена языковой особенностью.
DeadMG
@DeadMG: Нет, это не unique_ptrто, что было в основном включено языковой функцией. Это было бы не очень полезно без этой функции. потому что без идеальной пересылки во многих случаях это было бы бесполезно, а для идеальной пересылки требуется эта функция.
Ян Худек
2
  1. Gcc начал вводить C ++ 11 (все еще C ++ 0x в то время) в 4.3. Эта таблица говорит, что у нее уже есть ссылки на rvalue и некоторые другие менее используемые функции (вы должны указать -std=c++0xопцию, чтобы включить их).
  2. Многие дополнения к стандартной библиотеке в C ++ 11 уже были определены в TR1, и GNU stdlibc ++ предоставляет их в пространстве имен std :: tr1. Так что просто сделайте соответствующее условное использование.
  3. Boost определяет большинство функций TR1 и может внедрить их в пространство имен TR1, если у вас его нет (но VS2010 делает и gcc 4.3 делает то же самое, если вы используете GNU stdlibc ++).
  4. Помещение чего-либо в stdпространство имен - это «неопределенное поведение». Это означает, что в спецификации не сказано, что произойдет. Но если вы знаете, что на конкретной платформе стандартная библиотека не определяет что-то, просто определитесь. Просто ожидайте, что вам придется проверить на каждой платформе, что вам нужно и что вы можете определить.
  5. В предложении для ссылок rvalue, N1690 упоминается, как реализовать семантику перемещения в C ++ 03. Это может быть использовано для замены unique_ptr. Однако, это не было бы слишком полезно, потому что оно опирается на коллекции, фактически использующие семантику перемещения, а C ++ 03, очевидно, не будет.
Ян Худек
источник
1
Вы правы насчет GCC, но, к сожалению, я также должен поддерживать другие (не GCC) компиляторы. Ваша пуля № 4 лежит в основе вопроса, который я задаю. # 5 интересен, но я не собираюсь поддерживать семантику перемещения (оптимизацию копирования) на этих старых платформах, а просто "std :: move ()" как компилируемый синтаксис.
mcmcc