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
(как plain int
s), а только потом 06_AD
(не говоря уже о том, что август и сентябрь должны быть написаны без 0
восьмеричных чисел). Это можно обойти, сделав синтаксис 1974-1/6_AD
таким, чтобы порядок оценки оператора работал, но он был неуклюжим.
Итак, мой вопрос сводится к следующему: считаете ли вы, что эта функция себя оправдает? Какие еще литералы вы хотите определить, чтобы сделать ваш код на C ++ более читабельным?
Обновлен синтаксис для соответствия окончательному проекту от июня 2011 г.
string operator "" _s(const char*s);"
нельзя использовать для синтаксического анализа"hello"_s"
. Это строковый литерал, который будет искать оператор с дополнительнымsize_t
параметром. Я прав?uint16_t
, поведение которых зависит от реализации, на аналогичные типыuwrap16
иunum16
поведение которых не зависит от реализации, например, с учетомuwrap16 w=1; unum16 n=1;
выраженийw-2
иn-2
даст(uwrap16)65535
и(int)-1
, соответственно [uint16_t
даст первый результат в системах, гдеint
16 бит, а второй - в системах, гдеint
больше]. Самая большая проблема, которую я видел, - это обработка числовых литералов.sizeof
возвращение целочисленных типов, зависящих от реализации, но ситуацию все же можно было бы сделать намного лучше, чем она есть. Что бы вы подумали об этой концепции?Ответы:
Вот случай, когда есть преимущество использования определяемых пользователем литералов вместо вызова конструктора:
#include <bitset> #include <iostream> template<char... Bits> struct checkbits { static const bool valid = false; }; template<char High, char... Bits> struct checkbits<High, Bits...> { static const bool valid = (High == '0' || High == '1') && checkbits<Bits...>::valid; }; template<char High> struct checkbits<High> { static const bool valid = (High == '0' || High == '1'); }; template<char... Bits> inline constexpr std::bitset<sizeof...(Bits)> operator"" _bits() noexcept { static_assert(checkbits<Bits...>::valid, "invalid digit in binary string"); return std::bitset<sizeof...(Bits)>((char []){Bits..., '\0'}); } int main() { auto bits = 0101010101010101010101010101010101010101010101010101010101010101_bits; std::cout << bits << std::endl; std::cout << "size = " << bits.size() << std::endl; std::cout << "count = " << bits.count() << std::endl; std::cout << "value = " << bits.to_ullong() << std::endl; // This triggers the static_assert at compile time. auto badbits = 2101010101010101010101010101010101010101010101010101010101010101_bits; // This throws at run time. std::bitset<64> badbits2("2101010101010101010101010101010101010101010101010101010101010101_bits"); }
Преимущество состоит в том, что исключение времени выполнения преобразуется в ошибку времени компиляции. Вы не могли добавить статическое утверждение к битовому ctor, принимающему строку (по крайней мере, без аргументов строкового шаблона).
источник
На первый взгляд, это простой синтаксический сахар.
Но если заглянуть глубже, мы увидим, что это больше, чем синтаксический сахар, поскольку он расширяет возможности пользователя C ++ для создания пользовательских типов, которые ведут себя точно так же, как отдельные встроенные типы. В этом небольшом «бонусе» является очень интересное дополнение C ++ 11 к C ++.
А действительно ли это нужно в C ++?
Я вижу мало применений в коде, который я написал в последние годы, но то, что я не использовал его в C ++, не означает, что он не интересен для другого разработчика C ++ .
Мы использовали в C ++ (и, как мне кажется, в C) литералы, определенные компилятором, для ввода целых чисел как коротких или длинных целых чисел, вещественных чисел как чисел с плавающей запятой или двойных (или даже длинных двойных) и символьных строк как обычных или широких символов. .
В C ++ у нас была возможность создавать наши собственные типы (то есть классы), потенциально без накладных расходов (встраивание и т. Д.). У нас была возможность добавлять операторы к их типам, чтобы они вели себя как похожие встроенные типы, что позволяет разработчикам C ++ использовать матрицы и комплексные числа так же естественно, как если бы они были добавлены в сам язык. Мы даже можем добавить операторы приведения (обычно это плохая идея, но иногда это просто правильное решение).
Мы все еще упускаем одну вещь, чтобы пользовательские типы вели себя как встроенные типы: определяемые пользователем литералы.
Итак, я полагаю, что это естественная эволюция языка, но должна быть как можно более полной: « Если вы хотите создать тип, и вы хотите, чтобы он вел себя как можно больше как встроенные типы, вот инструменты. .. "
Я предполагаю, что это очень похоже на решение .NET сделать каждый примитив структурой, включая логические, целые числа и т. Д., И унаследовать все структуры от Object. Одно только это решение выводит .NET далеко за пределы возможностей Java при работе с примитивами, независимо от того, сколько хаков для упаковки / распаковки Java добавит в свою спецификацию.
ВАМ это действительно нужно на C ++?
На этот вопрос ВЫ должны ответить. Только не Бьярн Страуструп. Только не Херб Саттер. Ни один член комитета по стандартизации C ++. Вот почему у вас есть выбор в C ++ , и они не ограничивают полезную нотацию только встроенными типами.
Если вам это нужно, то это долгожданное дополнение. Если нет, что ж ... Не используйте это. Это вам ничего не будет стоить.
Добро пожаловать в C ++, язык, в котором функции не являются обязательными.
Вздутие ??? Покажи мне свои комплексы !!!
Есть разница между раздутым и сложным (каламбур).
Как показано Нильсом на сайте Какие новые возможности пользовательские литералы добавляют в 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, и тип «std :: complex» в C ++ можно умножать, складывать, вычитать и т. Д. С помощью перегрузки оператора.
Но в C99 они просто добавили еще один тип как встроенный тип и встроенную поддержку перегрузки операторов. И они добавили еще одну встроенную буквальную функцию.
В C ++ они просто использовали существующие функции языка, увидели, что буквальная функция является естественным развитием языка, и поэтому добавили ее.
В C, если вам нужно такое же улучшение обозначений для другого типа, вам не повезет, пока вы не начнете лоббировать добавление ваших квантовых волновых функций (или трехмерных точек, или любого другого базового типа, который вы используете в своей области работы) в Стандарт 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, но это зависит от ВАШИХ потребностей. В этом весь смысл позволить пользователю определять свои собственные буквальные имена.
источник
you can write 1+2i, but you still can't write a+bi, so there's absolutely no point
Даже игнорирование вашегоa+bi
примера смешно, тот факт, что вы воспринимаете его как «низкочастотный», не означает, что все это делают. . . Глядя на большую картину, цель состоит в том, чтобы убедиться, что определяемые пользователем объекты могут в максимальной степени считаться первоклассными гражданами языка, как и встроенные типы. Итак, если вы можете писать1.5f
и1000UL
, почему вы не можете писать25i
или даже100101b
? В отличие от C и Java, пользовательские типы не должны считаться второсортными гражданами языка C ++.Most of data still comes from IO
В коде много жестко запрограммированных значений. Посмотрите на все булевы, все целые числа, все двойные числа, которые входят в код, потому что удобнее писатьx = 2 * y ;
вместоx = Two * y
гдеTwo
- строго типизированная константа. Определяемые пользователем литералы позволяют нам присвоить ему тип и написать:x = 2_speed * y ;
и заставить компилятор проверить, имеет ли смысл вычисление. . . Все дело в строгой типизации. . . Возможно, вы не воспользуетесь им. Но я обязательно это сделаю, как только смогу использовать на работе компилятор с поддержкой C ++ 11.Это очень хорошо для математического кода. Я совершенно не понимаю, как можно использовать следующие операторы:
deg для градусов. Это делает запись абсолютных углов намного более интуитивной.
double operator ""_deg(long double d) { // returns radians return d*M_PI/180; }
Его также можно использовать для различных представлений с фиксированной точкой (которые все еще используются в области DSP и графики).
int operator ""_fix(long double d) { // returns d as a 1.15.16 fixed point number return (int)(d*65536.0f); }
Это хорошие примеры того, как его использовать. Они помогают сделать константы в коде более читабельными. Это еще один инструмент, позволяющий сделать код нечитаемым, но у нас уже есть так много злоупотреблений, что еще один не повредит.
источник
UDL имеют пространство имен (и могут быть импортированы с помощью объявлений / директив, но вы не можете явно использовать пространство имен как литерал
3.14std::i
), что означает (надеюсь) не будет тонны конфликтов.Тот факт, что они на самом деле могут быть шаблонными (и constexpr'd), означает, что вы можете делать некоторые довольно мощные вещи с помощью UDL. Авторы Bigint будут действительно счастливы, так как они, наконец, могут иметь сколь угодно большие константы, вычисляемые во время компиляции (с помощью constexpr или шаблонов).
Мне просто грустно, что мы не увидим в стандарте парочку полезных литералов (судя по всему), например
s
дляstd::string
иi
для мнимой единицы.Количество времени кодирования, которое будет сэкономлено UDL, на самом деле не так велико, но читаемость будет значительно увеличена, и все больше и больше вычислений можно будет переносить во время компиляции для более быстрого выполнения.
источник
Позвольте мне добавить немного контекста. Для нашей работы очень нужны литералы, определяемые пользователем. Мы работаем по MDE (Model-Driven Engineering). Мы хотим определять модели и метамодели на C ++. Фактически мы реализовали отображение Ecore на C ++ ( EMF4CPP ).
Проблема возникает при возможности определять элементы модели как классы в C ++. Мы используем подход преобразования метамодели (Ecore) в шаблоны с аргументами. Аргументами шаблона являются структурные характеристики типов и классов. Например, класс с двумя атрибутами int будет выглядеть примерно так:
typedef ::ecore::Class< Attribute<int>, Attribute<int> > MyClass;
Однако оказывается, что у каждого элемента в модели или метамодели обычно есть имя. Хотим написать:
typedef ::ecore::Class< "MyClass", Attribute< "x", int>, Attribute<"y", int> > MyClass;
НО, ни C ++, ни C ++ 0x этого не допускают, поскольку строки запрещены в качестве аргументов шаблонов. Вы можете написать имя char с помощью char, но это, по общему мнению, беспорядок. Имея правильные пользовательские литералы, мы могли бы написать нечто подобное. Скажем, мы используем "_n" для идентификации имен элементов модели (я не использую точный синтаксис, просто чтобы составить представление):
typedef ::ecore::Class< MyClass_n, Attribute< x_n, int>, Attribute<y_n, int> > MyClass;
Наконец, наличие этих определений в качестве шаблонов очень помогает нам разрабатывать алгоритмы для обхода элементов модели, преобразований модели и т.д., которые действительно эффективны, поскольку информация о типе, идентификация, преобразования и т.д. определяются компилятором во время компиляции.
источник
by the compiler at compile time
роль ... :-)Бьярн Страуструп рассказывает о UDL в своем выступлении на C ++ 11 , в первом разделе, посвященном интерфейсам с множеством типов, примерно через 20 минут.
Его основной аргумент в пользу UDL принимает форму силлогизма:
«Тривиальные» типы, т. Е. Встроенные примитивные типы, могут обнаруживать только тривиальные ошибки типов. Интерфейсы с расширенными типами позволяют системе типов улавливать больше видов ошибок.
Типы ошибок типов, которые может обнаружить богато типизированный код, влияют на реальный код. (Он приводит пример Mars Climate Orbiter, который, как известно, вышел из строя из-за ошибки размеров в важной константе).
В реальном коде единицы измерения используются редко. Люди не используют их, потому что выполнение вычислений или накладные расходы на память для создания расширенных типов обходятся слишком дорого, а использование уже существующего шаблонного кода модуля C ++ настолько уродливо с нотационной точки зрения, что его никто не использует. (Опытным путем никто не использует его, хотя библиотеки существуют уже десять лет).
Следовательно, чтобы заставить инженеров использовать единицы измерения в реальном коде, нам нужно устройство, которое (1) не требует дополнительных затрат времени выполнения и (2) является приемлемым с точки зрения записи.
источник
Единственное необходимое обоснование - это поддержка проверки размеров во время компиляции.
auto force = 2_N; auto dx = 2_m; auto energy = force * dx; assert(energy == 4_J);
См., Например, PhysUnits-CT-Cpp11 , небольшую библиотеку C ++ 11, C ++ 14, предназначенную только для заголовков, для анализа измерений во время компиляции, а также управления и преобразования единиц / количеств. Проще, чем Boost.Units , поддерживает литералы символов единиц, такие как m, g, s, метрические префиксы, такие как m, k, M, зависит только от стандартной библиотеки C ++, только SI, целых степеней измерений.
источник
Хм ... Я еще не задумывался об этой функции. Ваш образец был хорошо продуман и безусловно интересен. 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 ++.
Ах, в моем посте в основном ничего не говорится, кроме: все будет хорошо, влияние не будет слишком большим. Не волнуйтесь. :-)
источник
Я никогда не нуждался и не хотел в этой функции (но это мог быть эффект Blub ). Моя реакция коленного рефлекса заключается в том, что это неубедительно, и, вероятно, понравится тем же людям, которые думают, что это круто - перегружать operator + для любой операции, которая удаленно может быть истолкована как добавление.
источник
C ++ обычно очень строго относится к используемому синтаксису - за исключением препроцессора, мало что можно использовать для определения пользовательского синтаксиса / грамматики. Например, мы можем перегрузить существующие операции, но мы не можем определить новые - ИМО, это очень созвучно духу C ++.
Я не возражаю против некоторых способов более индивидуализированного исходного кода, но выбранный пункт кажется мне очень изолированным, что меня больше всего смущает.
Даже предполагаемое использование может значительно затруднить чтение исходного кода: одна буква может иметь обширные побочные эффекты, которые никак нельзя идентифицировать из контекста. При симметрии u, l и f большинство разработчиков выберут отдельные буквы.
Это также может превратить определение объема в проблему, использование отдельных букв в глобальном пространстве имен, вероятно, будет считаться плохой практикой, а инструменты, которые, как предполагается, упрощают смешивание библиотек (пространства имен и описательные идентификаторы), вероятно, не справятся с его назначением.
Я вижу некоторые достоинства в сочетании с «авто», а также в сочетании с библиотекой модулей , например, с модулями повышения , но этого недостаточно, чтобы заслужить это дополнение.
Однако мне интересно, какие умные идеи мы придумываем.
источник
using single letters in global namespace will probably be considered bad practice
Но это не имеет отношения: (A) UDL должны быть определены в области (неглобального) пространства имен ... предположительно потому, что (B) они должны состоять из символа подчеркивания, затем> = 1 буквы, а не только буквы, и таких идентификаторов в глобальные NS зарезервированы для реализации. Это как минимум 2 балла против идеи, что UDL изначально порождают путаницу. Что касается необходимости ограничивать пространство имен, уменьшая полезность функции, поэтому, например, stdlib объявляет их вinline namespace
s, которые пользователи могут импортировать оптом при желании.Я использовал пользовательские литералы для двоичных строк, например:
"asd\0\0\0\1"_b
используя
std::string(str, n)
конструктор, чтобы\0
не разрезать строку пополам. (Проект много работает с различными форматами файлов.)Это было полезно также, когда я отказался
std::string
от оболочки дляstd::vector
.источник
Линейный шум в этой штуке огромен. Также ужасно читать.
Дайте мне знать, аргументировали ли они это новое дополнение синтаксиса какими-либо примерами? Например, есть ли у них пара программ, которые уже используют 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
Ключевое слово «авто» может быть оправдано, но только возможно. Но возьмем только C ++, потому что в этом аспекте он лучше, чем C ++ 0x.
std::complex<double> val = std::complex(0, 3.14);
Это как .. так просто. Даже если подумать, что все стандартные и острые скобки просто неуместны, если вы используете их повсюду. Я не начинаю гадать, какой синтаксис есть в 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); в целом не сэкономит вам много времени, за исключением нескольких особых случаев.
источник
std::complex<double> val(0, 3.14);
.