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

С++ 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, так что порядок оценки оператора работает, но он неуклюж.

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


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

Ответ 1

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

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

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

Ответ 2

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

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

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

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

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

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

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

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

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

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

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

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

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

Раздутое??? Покажите мне свои комплексы!!!

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

Как показано Niels at Какие новые возможности добавляют пользовательские литералы к С++?, возможность записи сложного числа является одной из двух добавленных функций "недавно" на 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", так и С++ "std:: complex", могут быть умножены, добавлены, вычтены и т.д. с использованием перегрузки оператора.

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

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

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

В С++ 11 вы можете сделать это самостоятельно:

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

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

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

Пример даты?

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

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

Ответ 3

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

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

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

Ответ 4

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

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

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

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

Ответ 5

Позвольте мне добавить немного контекста. Для нашей работы требуются определенные пользователем литералы. Мы работаем над MDE (Model-Driven Engineering). Мы хотим определить модели и метамодели в С++. Фактически мы реализовали отображение из Ecore в С++ (EMF4CPP).

Проблема возникает, когда можно определить элементы модели как классы в С++. Мы применяем подход преобразования метамодели (Ecore) к шаблонам с аргументами. Аргументами шаблона являются структурные характеристики типов и классов. Например, класс с двумя атрибутами int будет выглядеть примерно так:

typedef ::ecore::Class< Attribute<int>, Attribute<int> > MyClass;

Hoever, оказывается, что каждый элемент в модели или метамодели, как правило, имеет имя. Мы хотели бы написать:

typedef ::ecore::Class< "MyClass", Attribute< "x", int>, Attribute<"y", int> > MyClass;

НО, С++ или С++ 0x не позволяют этого, поскольку строки запрещены как аргументы шаблонов. Вы можете написать имя char на char, но это признак беспорядка. С помощью правильных пользовательских литералов мы могли бы написать что-то подобное. Предположим, что мы используем "_n" для идентификации имен элементов модели (я не использую точный синтаксис, просто чтобы составить представление):

typedef ::ecore::Class< MyClass_n, Attribute< x_n, int>, Attribute<y_n, int> > MyClass;

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

Ответ 6

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

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

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

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

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

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

Ответ 7

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

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

assert(energy == 4_J); 

См., например, PhysUnits-CT-Cpp11, небольшую библиотеку заголовков С++ 11, С++ 14 для анализа размерности времени компиляции и манипуляции единицей/количеством и преобразование. Упрощен, чем Boost.Units, поддерживает условный символ литералы, такие как m, g, s, метрические префиксы, такие как m, k, M, зависит только от стандартной библиотеки С++, только SI, целых степеней размеров.

Ответ 8

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

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

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

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

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

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

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

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

Ответ 9

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

Ответ 10

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

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

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

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

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

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

Ответ 11

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

 "asd\0\0\0\1"_b

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

Это было полезно также, когда я удалил std::string в пользу обертки для std::vector.

Ответ 12

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

Сообщите мне, они объяснили, что новое синтаксическое дополнение с любыми примерами? Например, есть ли у них несколько программ, которые уже используют С++ 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 может быть оправданным, хотя, возможно. Но давайте возьмем только С++, потому что это лучше, чем С++ 0x в этом аспекте.

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

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

complex = std::complex<double>;

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

typedef std::complex<double> complex;

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

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

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