[C++] Какие новые возможности добавляют пользовательские литералы к C ++?


Answers

На первый взгляд кажется, что это простой синтаксический сахар.

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

Нам действительно нужно это на C ++?

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

Мы использовали в C ++ (и в C, я полагаю), компилятор-определенные литералы, чтобы вводить целочисленные числа как короткие или длинные целые числа, реальные числа как float или double (или даже long double), и символьные строки как обычные или широкие символы ,

В C ++ у нас была возможность создавать наши собственные типы (т.е. классы), причем потенциально не накладные расходы (inlining и т. Д.). У нас была возможность добавлять операторов к их типам, чтобы они вели себя подобно подобным встроенным типам, что позволяет разработчикам C ++ использовать матрицы и сложные номера так же естественно, как если бы они были добавлены к самому языку. Мы можем даже добавить операторов литья (что обычно плохое, но иногда это просто правильное решение).

Мы все еще пропустили одну вещь, чтобы пользовательские типы вели себя как встроенные типы: пользовательские литералы.

Итак, я предполагаю, что это естественная эволюция для языка, но для того, чтобы быть как можно более полным: « Если вы хотите создать тип, и вы хотите, чтобы он вел себя так же хорошо, как встроенные типы, вот инструменты. .. "

Я бы предположил, что это очень похоже на решение .NET сделать каждый примитив структурой, включая логические, целые и т. Д., И все структуры получаются из Object. Это решение само по себе ставит .NET далеко за пределы возможностей Java при работе с примитивами, независимо от того, насколько сильно будут добавлены Java-приложения для бокса / распаковки Java.

Действительно ли вам это нужно на C ++?

Этот вопрос для ВАС должен ответить. Не Бьярне Страуструп. Не трава Саттер. Не кто-либо из членов стандартного комитета C ++. Вот почему у вас есть выбор на C ++ , и они не будут ограничивать полезную нотацию только встроенными типами.

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

Добро пожаловать в C ++, язык, где функции являются необязательными.

Bloated ??? Покажи мне свои комплексы !!!

Существует различие между раздутыми и сложными (каламбур).

Как показано Нильсом в разделе Какие новые возможности добавляют пользовательские литералы к C ++? , возможность написать комплексное число является одной из двух функций, добавленных «недавно» на C и C ++:

// C89:
MyComplex z1 = { 1, 2 } ;

// C99: You'll note I is a macro, which can lead
// to very interesting situations...
double complex z1 = 1 + 2*I;

// C++:
std::complex<double> z1(1, 2) ;

// C++11: You'll note that "i" won't ever bother
// you elsewhere
std::complex<double> z1 = 1 + 2_i ;

Теперь, как C99 «double complex», так и тип C ++ «std :: complex», можно умножать, добавлять, вычитать и т. Д., Используя перегрузку оператора.

Но в C99 они просто добавили другой тип как встроенный тип и встроенную поддержку перегрузки оператора. И они добавили еще одну встроенную литеральную функцию.

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

В C, если вам нужно такое же усовершенствование нотации для другого типа, вам не повезло, пока ваше лоббирование не добавит ваши квантовые волновые функции (или 3D-точки, или какой-либо базовый тип, который вы используете в своей области работы) к Стандарт C как встроенный тип преуспевает.

В C ++ 11 вы можете сделать это сами:

Point p = 25_x + 13_y + 3_z ; // 3D point

Разве это раздуто? Нет , есть необходимость, как показано, как как C, так и C ++-комплексы нуждаются в способе представления своих литеральных комплексных значений.

Это неправильно спроектировано? Нет , он разработан как любая другая функция C ++ с учетом расширяемости.

Это только для обозначения целей? Нет , поскольку это может даже добавить безопасность типа к вашему коду.

Например, давайте представим CSS-ориентированный код:

css::Font::Size p0 = 12_pt ;       // Ok
css::Font::Size p1 = 50_percent ;  // Ok
css::Font::Size p2 = 15_px ;       // Ok
css::Font::Size p3 = 10_em ;       // Ok
css::Font::Size p4 = 15 ;         // ERROR : Won't compile !

Тогда очень легко обеспечить сильную типизацию присвоения значений.

Это опасно?

Хороший вопрос. Могут ли эти функции размещаться по именам? Если да, то Джекпот!

В любом случае, как и все, вы можете убить себя, если инструмент используется ненадлежащим образом . C мощный, и вы можете стрелять в голову, если вы злоупотребляете пистолетом C. C ++ имеет пистолет C, но также скальпель, тазер и любой другой инструмент, который вы найдете в наборе инструментов. Вы можете неправильно использовать скальпель и убить себя до смерти. Или вы можете создать очень элегантный и надежный код.

Итак, как и любая функция C ++, вам это действительно нужно? Это вопрос, на который вы должны ответить, прежде чем использовать его на C ++. Если вы этого не сделаете, это ничего не будет стоить вам. Но если вам это действительно нужно, по крайней мере, язык вас не подведет.

Пример даты?

Ваша ошибка, мне кажется, заключается в том, что вы смешиваете операторы:

1974/01/06AD
    ^  ^  ^

Этого нельзя избежать, потому что / являясь оператором, компилятор должен его интерпретировать. И, AFAIK, это хорошо.

Чтобы найти решение вашей проблемы, я бы написал литерал каким-то другим способом. Например:

"1974-01-06"_AD ;   // ISO-like notation
"06/01/1974"_AD ;   // french-date-like notation
"jan 06 1974"_AD ;  // US-date-like notation
19740106_AD ;       // integer-date-like notation

Лично я бы выбрал целое число и даты ISO, но это зависит от ВАШИХ потребностей. В этом и заключается цель дать возможность пользователю определить собственные литералы.

Question

C++11 вводит определяемые пользователем литералы, которые позволят ввести новый литерал-синтаксис на основе существующих литералов ( int , hex , string , float ), чтобы любой тип мог иметь буквенное представление.

Примеры:

// imaginary numbers
std::complex<long double> operator "" _i(long double d) // cooked form
{ 
    return std::complex<long double>(0, d); 
}
auto val = 3.14_i; // val = complex<long double>(0, 3.14)

// binary values
int operator "" _B(const char*); // raw form
int answer = 101010_B; // answer = 42

// std::string
std::string operator "" _s(const char* str, size_t /*length*/) 
{ 
    return std::string(str); 
}

auto hi = "hello"_s + " world"; // + works, "hello"_s is a string not a pointer

// units
assert(1_kg == 2.2_lb); // give or take 0.00462262 pounds

На первый взгляд это выглядит очень круто, но мне интересно, насколько это применимо, когда я пытался подумать о том, что суффиксы _AD и _BC создают даты, которые я обнаружил, что это проблема из-за порядка оператора. 1974/01/06_AD будет сначала оценивать 1974/01 (как обычные int s), а только позже 06_AD (не говоря уже о августе и сентябре, которые должны быть записаны без 0 для восьмизначных причин). Это можно обойти, имея синтаксис в 1974-1/6_AD так что порядок оценки оператора работает, но он неуклюж.

Так что мой вопрос сводится к тому, что вы чувствуете, что эта функция оправдает себя? Какие другие литералы вы хотели бы определить, что сделает ваш код на C ++ более удобочитаемым?

Обновленный синтаксис, чтобы соответствовать окончательному проекту в июне 2011 года




Bjarne Stroustrup рассказывает о UDL в этом разговоре на C ++ 11 , в первом разделе на интерфейсах с богатым интерфейсом, около 20-минутной отметки.

Его основной аргумент в отношении UDLs носит форму силлогизма:

  1. «Тривиальные» типы, т. Е. Встроенные примитивные типы, могут захватывать только тривиальные ошибки типа. Интерфейсы с более богатыми типами позволяют системе типов обнаруживать больше ошибок.

  2. Типы ошибок типов, которые могут набирать богатый код, могут повлиять на реальный код. (Он приводит пример орбитального аппарата Марса, который позорно провалился из-за ошибки размеров в важной константе).

  3. В реальном коде единицы редко используются. Люди не используют их, потому что чрезмерные затраты времени на выполнение или избыточные ресурсы памяти для создания богатых типов являются слишком дорогостоящими, и использование ранее существовавшего кода на языке C ++ с шаблоном настолько условно уродливым, что его никто не использует. (Эмпирически никто ее не использует, хотя библиотеки существуют уже около десяти лет).

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




Линейный шум в этой вещи огромен. Также ужасно читать.

Дайте мне знать, объяснили ли они, что новое дополнение синтаксиса с любыми примерами? Например, у них есть несколько программ, которые уже используют C ++ 0x?

Для меня эта часть:

auto val = 3.14_i

Не оправдывает эту часть:

std::complex<double> operator ""_i(long double d) // cooked form
{ 
    return std::complex(0, d);
}

Даже если вы будете использовать i-синтаксис в 1000 других строк. Если вы пишете, вы, вероятно, также напишите 10000 строк чего-то еще. Особенно, когда вы, вероятно, будете писать, в основном, везде:

std::complex<double> val = 3.14i

«auto» -keyword может быть оправданным, хотя, возможно. Но давайте возьмем только C ++, потому что в этом аспекте он лучше C ++ 0x.

std::complex<double> val = std::complex(0, 3.14);

Это как .. это просто. Даже подумал, что все std и pointy brackets просто хромые, если вы используете его везде. Я не начинаю угадывать, какой синтаксис существует в C ++ 0x для превращения std :: complex в сложный.

complex = std::complex<double>;

Это, возможно, что-то прямое, но я не считаю, что это просто в C ++ 0x.

typedef std::complex<double> complex;

complex val = std::complex(0, 3.14);

Может быть? > :)

Во всяком случае, дело в следующем: запись 3.14i вместо std :: complex (0, 3.14); не сэкономит вам много времени в целом, за исключением нескольких особых случаев.




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

assert(1_kg == 2.2_lb); // give or take 0.00462262 pounds

... Интересно, как вы это выразите сегодня. У вас будет класс KG и LB, и вы будете сравнивать неявные объекты:

assert(KG(1.0f) == LB(2.2f));

И это тоже будет. С типами, у которых есть более длинные имена или типы, на которые у вас нет надежды иметь такой красивый конструктор для написания адаптера без него, это может быть хорошим дополнением к созданию и инициализации неявного объекта «на лету». С другой стороны, вы уже можете создавать и инициализировать объекты с помощью методов.

Но я согласен с Нилсом в математике. Функции тригонометрии C и C ++, например, требуют ввода в радианах. Я думаю, что в степенях, хотя очень короткое неявное преобразование, такое как Nils, было очень приятным.

В конечном счете, это будет синтаксический сахар, но это будет иметь небольшое влияние на читаемость. И, вероятно, будет легче написать некоторые выражения тоже (sin (180.0deg) легче написать, чем sin (deg (180.0)). И тогда будут люди, которые злоупотребляют концепцией. Но тогда люди, злоупотребляющие языком, должны использовать очень ограничительные языки, а не что-то выразительное, как C ++.

Ах, мой пост говорит в основном ничего, кроме: все будет хорошо, удар не будет слишком большим. Давайте не будем волноваться. :-)




UDLs имеют пространство имен (и могут быть импортированы с помощью деклараций / директив, но вы не можете явно использовать 3.14std::i как литерал, такой как 3.14std::i ), что означает, что там (надеюсь) не будет тонны столкновений.

Тот факт, что они могут быть шаблонами (и constexpr'd), означает, что вы можете сделать некоторые довольно мощные вещи с UDL. Авторы Bigint будут действительно счастливы, так как они могут, наконец, иметь произвольно большие константы, рассчитанные во время компиляции (через constexpr или templates).

Мне просто грустно, что мы не увидим пару полезных литералов в стандарте (по внешнему виду), например s для std::string и i для мнимой единицы.

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




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

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

Даже предполагаемое использование может усложнить чтение исходного кода: одно письмо может иметь обширные побочные эффекты, которые никоим образом не могут быть идентифицированы из контекста. С симметрией к u, l и f большинство разработчиков выбирают отдельные буквы.

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

Я вижу некоторые достоинства в сочетании с «авто», также в сочетании с библиотекой единиц , такими как блоки ускорения , но недостаточно для того, чтобы заслужить это признание.

Интересно, однако, какие умные идеи мы придумываем.




Links