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

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

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


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

12 ответов


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

#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..., ''});
  }

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, я думаю), определяемые компилятором литералы, для ввода целых чисел как коротких или длинных целых чисел, вещественных чисел как float или double (или даже long double), а символьные строки как обычные или широкие символы.

в C++ у нас была возможность создавать собственные типы (т. е. классы), потенциально без накладных расходов(inlining и т. д.). У нас был возможность добавлять операторы к своим типам, чтобы они вели себя как аналогичные встроенные типы, что позволяет разработчикам 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" двойной сложный " тип и C++ "std::complex" тип могут быть умножены,добавлены, вычитаются и т.д. используя перегрузку операторов.

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

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

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

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

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

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

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

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

например, давайте представим 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 !

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

Is опасно?

хороший вопрос. Могут ли эти функции быть пространствами имен? Если да, то джекпот!

в любом случае,как и все, вы можете убить себя, если инструмент используется неправильно. 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, но это зависит от ваших потребностей. В этом весь смысл, позволяя пользователю определять свои собственные литеральные имена.


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

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

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

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


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

тот факт, что они на самом деле могут быть шаблонных (и таких бы) означает, что вы можете сделать некоторые довольно мощная штука с универсальные связи данных. Авторы 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 talk, в первом разделе о интерфейсах с богатым типом, около 20 минут.

его основной аргумент для универсальные связи данных принимает форму силлогизма:

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

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

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

  4. поэтому, чтобы заставить инженеров использовать единицы в реальном коде, нам нужно устройство, которое (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 только для компиляционного анализа размеров и обработки и преобразования единиц/количеств. Проще, чем импульс.Единицы поддерживает символ блок литералы, такие как 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));

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

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

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

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


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


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

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

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

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

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

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


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

 "asd"_b

используя std::string(str, n) конструктор так что не разрезать нитку пополам. (Проект выполняет большую работу с различными форматами файлов.)

Это было полезно также, когда я угробил 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);

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

complex = std::complex<double>;

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

typedef std::complex<double> complex;

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

возможно? >:)

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