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

StackOverflow https://stackoverflow.com/questions/237804

Вопрос

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 (как обычный ints) и только позже 06_AD (не говоря уже о том, что август и сентябрь должны быть написаны без 0 по восьмеричным причинам).Это можно обойти, установив синтаксис следующим образом 1974-1/6_AD таким образом, порядок оценки оператора работает, но он неуклюжий.

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


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

Это было полезно?

Решение

Вот случай, когда есть преимущество в использовании пользовательских литералов вместо вызова конструктора:

#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");
}

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

Другие советы

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

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

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

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

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

В 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 "double complex" и C ++ "std::complex" могут быть умножены, добавлены, вычитаемы и т.д., используя перегрузку оператора.

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

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

В 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
    ^  ^  ^

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

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

"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, но это зависит от ВАШИХ потребностей.В этом весь смысл того, чтобы позволить пользователю определять свои собственные литеральные имена.

Это очень хорошо для математического кода.Из моего разума я вижу применение для следующих операторов:

град для обозначения градусов.Это делает написание абсолютных углов намного более интуитивно понятным.

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), означает, что вы можете делать некоторые довольно мощные вещи с UDLS.Авторы Bigint будут по-настоящему счастливы, так как они наконец-то смогут иметь сколь угодно большие константы, вычисляемые во время компиляции (с помощью constexpr или шаблонов).

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

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

Позвольте мне добавить немного контекста.Для нашей работы крайне необходимы определяемые пользователем литералы.Мы работаем по 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;

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

Бьярне Страуструп рассказывает об UDL в этом Обсуждение C ++ 11, в первом разделе, посвященном интерфейсам с большим количеством типов, около 20-минутной отметки.

Его основной аргумент в пользу UDLS принимает форму силлогизма:

  1. "Тривиальные" типы, то есть встроенные примитивные типы, могут улавливать только ошибки тривиального типа.Интерфейсы с более богатыми типами позволяют системе типов улавливать больше видов ошибок.

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

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

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

Поддержка проверки измерений во время компиляции - это единственное требуемое обоснование.

auto force = 2_N; 
auto dx = 2_m; 
auto energy = force * dx; 

assert(energy == 4_J); 

Смотрите, например Физические блоки-CT-Cpp11, небольшая библиотека только для заголовков C ++ 11, C ++ 14 для размерного анализа во время компиляции и манипулирования единицами измерения / количеством и преобразования.Проще , чем Повышение.Единицы измерения, поддерживает символ единицы измерения литералы, такие как 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 ++, например, требуют ввода в радианах.Однако я думаю в градусах, поэтому очень короткое неявное преобразование, подобное опубликованному Нильсом, очень приятно.

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

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

Я никогда не нуждался или не хотел эту функцию (но это могло бы быть Рыдать эффект).Моя реакция на коленный рефлекс заключается в том, что это неубедительно и, вероятно, понравится тем же людям, которые думают, что это круто перегружать operator + для любой операции, которая удаленно может быть истолкована как добавление.

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

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

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

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

Я вижу некоторые преимущества в сочетании с "auto", а также в сочетании с библиотекой модулей, такой как единицы наддува, но недостаточно, чтобы заслужить это признание.

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

Я использовал пользовательские литералы для двоичных строк, подобных этому:

 "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

однако ключевое слово 'auto' может быть оправдано, но только возможно.Но давайте возьмем только C ++, потому что в этом аспекте он лучше, чем C ++ 0x.

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

Это как..вот так просто.Даже думал, что все std и заостренные скобки просто убогие, если вы используете их практически везде.Я не начинаю гадать, какой синтаксис есть в C ++ 0x для превращения std::complex в 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);в целом это не экономит вам много времени, за исключением нескольких особых случаев.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top