Синтаксический сахар в C / C ++

Я изучал Ruby и нашел его ключевые слова «до» и «до» очень интересными. Поэтому я подумал, что это хороший способ добавить похожие ключевые слова в C / C ++. Вот что я придумал:

#define until(x)    while(!(x))
#define unless(x)   if(!(x))

Я ищу несколько предложений по этому поводу. Может ли кто-нибудь предложить лучшую альтернативу?

Вот пример программы, которую я написал, чтобы проиллюстрировать свои намерения:

#include <stdio.h>
#include <stdlib.h>

#define until(x)    while(!(x))
#define unless(x)   if(!(x))

unsigned int factorial(unsigned int n) {
    unsigned int fact=1, i;
    until ( n==0 )
        fact *= n--;
    return fact;    
}

int main(int argc, char*argv[]) {
    unless (argc==2)
        puts("Usage: fact <num>");
    else {
        int n = atoi(argv[1]);
        if (n<0)
            puts("please give +ve number");
        else
            printf("factorial(%u) = %u\n",n,factorial(n));
    }
    return 0;
}

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


person BiGYaN    schedule 09.04.2011    source источник
comment
Похоже, ты уже это сделал.   -  person Thiago Cardoso    schedule 09.04.2011
comment
хотя поначалу они выглядят очень модно, более уместно вместо этого использовать стандартный C ++, даже если вы программируете только для себя. В командной работе это недопустимо. Ваш код будет выглядеть незнакомым. Я помню некий sugar.h, содержащий определения типа Паскаля ...   -  person mbx    schedule 09.04.2011
comment
Я не думаю, что мне нравится unless с предложением else (однако мне нравится, когда / если в lisp). Меня не особо впечатлил цикл until, но, возможно, потому, что я никогда его раньше не использовал.   -  person 6502    schedule 09.04.2011
comment
Я не понимаю, насколько это полезно. Зачем два новых ключевых слова для чего-то, что логично и легко сделать с существующими?   -  person houbysoft    schedule 10.04.2011
comment
@houbysoft: хотя я согласен, что это плохая практика, можно утверждать, что выразительность - это достоинство. делайте это до тех пор, пока это не станет более выразительным, чем это, но не так, даже если они означают одно и то же. Вернее, они выражают разные вещи; завершающее условие, а не продолжающееся условие. Подобно тому, как while можно легко реализовать через if... goto, но первый for кратко выражает ваше желание зацикливаться.   -  person Dennis Zickefoose    schedule 10.04.2011
comment
Я согласен, что это плохая практика. Я не собираюсь делать это с помощью кода или чего-либо, связанного с другими программистами. Тем не менее, я нахожу это довольно изящным для коротких кодов (в основном для соревнований по кодированию или задач алгоритмов).   -  person BiGYaN    schedule 10.04.2011
comment
Возможно, вас заинтересует этот проект: lolcode.com, который также добавляет немного синтаксического сахара ...: D   -  person Inverse    schedule 22.04.2011
comment
Если вы должны это сделать, я предлагаю только правильно написать until.   -  person Zach Rattner    schedule 22.04.2011
comment
Я предлагаю не использовать define #define, а сделать транспилятор, чтобы люди могли читать его на C.   -  person Caveman    schedule 01.10.2018


Ответы (10)


То, как вы это сделали, мне кажется правильным, если вы вообще собираетесь это делать. Поскольку расширение макроса настолько похоже на то, что вы ожидаете [1], я считаю правильным сделать макрос похожим на syntax (), а не на обычно рекомендуемые SCARY_UPPERCASE_MACROS (), которые используется, чтобы показать, что этот код не следует обычному синтаксису, и вы должны использовать его только осторожно.

[1] Единственным недостатком является невозможность объявления переменных, что в любом случае маловероятно и может привести к ошибке в нужном месте при неправильном использовании, вместо того, чтобы делать что-то странное.

Кроме того, важно даже небольшое увеличение читабельности, поэтому возможность сказать until ( вместо while (! действительно облегчает чтение многих циклов. Если конечное условие легче рассматривать как исключительное условие (независимо от того, есть оно или нет), запись цикла таким образом облегчает чтение. Так что, хотя это всего лишь синтаксический сахар, я думаю, есть причина его рассмотреть.

Однако я не думаю, что оно того стоит. Преимущество невелико, так как большинство программистов привыкли читать if (!, а цена вполне реальна: любой, кто читает код, должен будет проверить, является ли это макросом или специальным компилятором, и делает ли он то, что они думают. И это может ввести вас в заблуждение, заставив вас думать, что вы можете делать такие вещи, как i=5 unless xxxx;. Такие небольшие улучшения, если они будут широко распространены, фрагментируют язык, поэтому часто лучше делать что-то стандартным способом и медленно внедрять улучшения.

Тем не менее, это можно сделать хорошо: все функции boost и tr1, особенно то, что сделано с шаблонами, чтобы они выглядели как расширения библиотеки, включают в себя расширение C ++ различными способами, многие из которых не приняты, поскольку они не казались стоящими это, но многие из которых получили незначительное или очень широкое распространение, потому что они внесли реальные улучшения.

person Jack V.    schedule 12.04.2011
comment
Первый абзац этого ответа, мягко говоря, проблематичен! Макросы не имеют пространства имен; они полностью глобальны. Так что ужасно давать им короткие имена, которые могут быть использованы где-то еще. Если вы определяете макрос с коротким именем, например until, а затем включаете сторонний заголовок, который объявляет функцию-член until, этот заголовок будет поврежден. Он попытается объявить void until(int a);, но вместо этого будет говорить void while(!(int a)); Удачи в расшифровке этого сообщения об ошибке! И поэтому (неудивительно) Boost использует устрашающие имена в верхнем регистре с префиксами для всех своих макро-трюков. - person Daniel Earwicker; 11.11.2013

Может ли кто-нибудь предложить лучшую альтернативу?

да. Не делай этого вообще. Просто используйте операторы while и if напрямую.

Когда вы программируете на C или C ++, программируйте на C или C ++. Хотя until и unless могут использоваться часто и идиоматично в некоторых языках, их нет в C или C ++.

person James McNellis    schedule 09.04.2011
comment
+1 по этому поводу. Возможно, это идиоматично делать это в Ruby, но вы же не программируете на Ruby, не так ли? Не пытайтесь заставить язык действовать как другой, вы просто так напишете плохой код. - person Mike Bailey; 09.04.2011
comment
Я знаю, что в такой большой среде кодирование только навлечет на себя неприятности. Но мой вопрос заключался в том, как лучше всего это сделать; а не о том, делать это или нет. - person BiGYaN; 10.04.2011
comment
@BiGYaN: Лучший способ сделать это - это вообще не делать этого. - person James McNellis; 10.04.2011
comment
Это! Полезно. - person n-smits; 14.07.2020

Это напомнило мне кое-что, что я видел в чьем-то коде:

#define R return;

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

person BЈовић    schedule 09.04.2011

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

Вы не можете использовать их в стиле Ruby как

`printf("hello,world") unless(a>0);`

незаконно.

И программистам на C будет сложнее понять код. Между тем дополнительный макрос может быть проблемой.

person xis    schedule 09.04.2011
comment
a ›0 && printf (Привет, мир!); ? - person Martín Fixman; 12.04.2011

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

Так что, если бы ваш макрос назывался BIGYAN_UNNECESSARY_MACRO_UNTIL, то это было бы не совсем «за гранью».

Если вы хотите расширить C ++ новыми конструкциями циклов, рассмотрите возможность изучения лямбда-выражений в C ++ 0x, где вы могли бы разрешить:

until([&] { return finished; }, [&] 
{
    // do stuff
});

Не идеально, но лучше макросов.

person Daniel Earwicker    schedule 09.04.2011
comment
Предлагаемое вами использование лямбда-выражений вызовет у меня кошмары сегодня вечером. - person 6502; 09.04.2011
comment
Я в противоречии: использование лямбд одновременно и ужасно, и потрясающе. Хотя мне это нравится. - person James McNellis; 09.04.2011
comment
Для чего-то столь же простого, как повторная реализация while(!b), это явно перебор. - person Daniel Earwicker; 10.04.2011
comment
Это действительно ужасно и потрясающе. Однако это вполне нормальный код (и довольно чистый) на языках программирования FP. Возможность определять собственную структуру управления очень полезна. - person ysdx; 10.04.2011

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

until (T* p = f(x)) ...
unless (T* p = f(x)) ...

с другой стороны, мы можем написать так:

while (T* p = f(x)) ...
if (T* p = f(x)) ...

Что касается unless, если мы определим его как:

#define unless(x) if (x) {} else

тогда мы можем написать unless (T* p = f(x)) .... Однако в этом случае мы не можем добавить после него предложение else.

person Ise Wisteria    schedule 09.04.2011
comment
Я только что был программистом на C, это привлекло мое внимание. Вы не можете сделать это в C (вопрос отмечен как C, так и C ++). Я с трудом могу поверить, что вы можете сделать это на C ++ ... Зачем вам это вообще нужно? - person James Morris; 10.04.2011
comment
@JamesMorris: Да, недостатки верны только тогда, когда мы используем макросы в C ++. Спасибо, что указали. Я отредактировал ответ. - person Ise Wisteria; 10.04.2011

Хороший пример синтаксического сахара (ИМХО):

struct Foo {
    void bar() {}
};
typedef std::vector<Foo*> FooVector;
typedef boost::ptr_vector<Foo> FooPtrVector;

FooVector v1;
for (FooVector::iterator it = v1.begin(); it != v1.end(); ++it)
    (*it)->bar(); // ugly

FooPtrVector v2;
for (FooPtrVector::iterator it = v2.begin(); it != v2.end(); ++it)
    it->bar(); // nice
person Torsten    schedule 22.08.2012

Посмотрите, как делается boost foreach.

Заголовок определяет BOOST_FOREACH (уродливый макрос с префиксом). Ты сможешь

#define foreach BOOST_FOREACH

в ваших файлах .cpp, чтобы иметь более чистый код. Однако вам не следует делать это в ваших файлах .h и вместо этого использовать уродливый BOOST_FOREACH.

Теперь, вот набор макросов «в стиле функционального программирования» для «удобных» выражений IF THEN ELSE (потому что?: Некрасиво):

#define IF(x) (x) ?
#define ELSE :

в настоящее время

int x = IF(y==0) 1
        ELSE IF(y<0) 2*y
        ELSE 3*y;

обессахаривается в:

int x = (y==0) ? 1 : (y<0) ? 2*y : 3*y;
person ysdx    schedule 09.04.2011

Как говорили люди, добавление этого слова на самом деле не дает полезного синтаксического сахара, потому что стоимость чтения (или if (!) Мала, к этому привыкли все разработчики C, и, используя такой макрос, вы напугаете большую часть Разработчики на C. Кроме того, делать язык похожим на другой - не лучшая идея.

НО, синтаксический сахар имеет значение. Как уже говорилось, в C ++ boost добавляет много синтаксического сахара с помощью шаблонов, а stl также предоставляет сахар Somme (например, std::make_pair(a, b) является синтаксическим сахаром для std::pair<decltype(a), decltype(b)>(a, b).

По мере улучшения языка добавляются как функциональные возможности, так и синтаксический сахар, чтобы улучшить читаемость, удобство записи и эффективность разработчиков. Например, в спецификации C ++ 11 было добавлено «for (elements in datastructure)» (см. Ниже), а также ключевое слово «auto», которое позволяет вывод типов в течение недели (я говорю «слабый», потому что вам нужно ввести много типов во многих местах, где тип на самом деле «очевиден» и избыточен).

Кроме того, в haskell использование монад без нотации do (синтаксический сахар) было бы настоящей проблемой, и никто бы не стал их использовать 1.


Пример без синтаксического сахара:

//C++ < 11
std::vector<int> v;
v.push_back(3);
v.push_back(7);
v.push_back(9);
v.push_back(12);
for (std::vector<int>::iterator it = v.begin();
     it != v.end();
     it++)
{
    std::cout << *it << std::endl;
}

И с синтаксическим сахаром:

//C++ >= 11
std::vector<int> v {3, 7, 9, 12};

for (auto elm : v)
{
    std::cout << elm << std::endl;
}

Немного читабельнее, не так ли?


Пример haskell для монады ввода-вывода (из HaskellWiki):

f :: IO String
f =
  ask "What's your name ?" >>= \name ->
  putStrLn "Write something." >>= \_ ->
  getLine >>= \string ->
  putStrLn ("Hello " ++ name ++ " you wrote " ++ string) >>= \_ ->
  return name

g :: IO String    
g = do
  name <- ask "What's your name ?"
  putStrLn "Write something."
  string <- getLine
  putStrLn ("Hello " ++ name ++ " you wrote " ++ string)
  return name

Вот ссылка на ideone: http://ideone.com/v9BqiZ


1: на самом деле язык более гибкий, чем C ++, и позволяет создавать операторы (например, & ^, +.,: + :, ...), так что мы могли представить, что кто-то снова быстро вводит синтаксический сахар :).

person Jeremy Cochoy    schedule 22.07.2013

Что ж, вы могли бы это сделать, но убедитесь, что этого нет в исходном файле. Я рекомендую использовать подход CoffeeScript к JavaScript без генерации оптимизации.

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

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

Удачи.

person Caveman    schedule 01.10.2018