Какие новые возможности пользовательские литералы добавляют в C ++? - PullRequest
137 голосов
/ 26 октября 2008

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

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


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

Ответы [ 12 ]

190 голосов
/ 26 октября 2008

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

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

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

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

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

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

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

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

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

Тебе действительно нужно это в C ++?

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

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

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

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

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

Как показано Niels на Какие новые возможности пользовательские литералы добавляют в 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

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

69 голосов
/ 26 октября 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");
}

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

36 голосов
/ 26 октября 2008

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

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

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

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

17 голосов
/ 26 октября 2008

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

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

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

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

12 голосов
/ 29 октября 2010

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

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

10 голосов
/ 09 сентября 2013

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

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

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

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

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

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

8 голосов
/ 02 июля 2016

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

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, интегральные степени измерений.

6 голосов
/ 26 октября 2008

Хм ... Я еще не думал об этой функции. Ваш образец был хорошо продуман и, безусловно, интересен. 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.0deg) легче написать, чем sin (deg (180.0)). И тогда найдутся люди, которые злоупотребляют этой концепцией. Но тогда люди, злоупотребляющие языком, должны использовать очень строгие языки, а не такие выразительные, как C ++.

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

3 голосов
/ 26 октября 2008

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

2 голосов
/ 27 августа 2015

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

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

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

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

...