Pull to refresh

Comments 35

UFO just landed and posted this here

Есть некоторое ощущение , что если у вас 5 флагов в сигнатуре метода, то вы накосячили сильно раньше и исправлять надо явно не параметры.

Для нас в похожем случае лучшим выходом было изменение имени функции, по типу:

bool MatchCtrlAltShift(bool ctrl, bool alt, bool shift);

и никаких проблем ни с чтением ни написанием.

и никаких проблем ни с чтением ни написанием.

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

vall = MatchCtrlAltShift(true, true, false);

Я думаю что средствами языка эту проблему решить невозможно. Там всё очень хрупкое, одно неверное движение, и тут-же появится толпы невинно обиженных. Но есть вариант решения в виде плагина. Всплывающая подсказка в любой ide - как самое простое решение начального уровня. Нужно просто расширить полномочия - чтобы просмоторщик имел право менять текст на экране. Дополнять используемые функции именами переменных, есно другим цветом. Есно в режиме наложения изображения - для исключения ошибок копировать/вставить.

Подобный экспериментальный плагин просмоторщика уже делали для С::B. И столкнулись с огромной кучей проблем. Чтоб плагин работал корректно - нужно переписать не только С::B, но и фреймворки из которых он создаётся. То-есть там изначально нет места для маневрирования. А в других IDE всё намного печальнее - там нужно начинать с чистого листа.

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

Всё написанное касается изменённого отображения параметров функций.

Удивительно, но такая фича уже существует, причем не вчера появилась. Называется inlay hints.

Выглядит плюс-минус так:

IntelliJ
VS Code
Это будет работать в IDE, при наведении курсора на функцию будет всплывать подсказка
Как я понял, идея в том, чтобы названия параметров включить в имя ф-ции, т.е. вместо
void Render(double X, double Y, double Z, bool Optimize, bool Final);
писать
void RenderXYZOptmzFinal(double X, double Y, double Z, bool Optimize, bool Final);
У идеи есть свои минусы, но право на жизнь тоже имеет.
Зачем вам C++20? Чем так хреново?
struct RenderGlyphsOptions {
  bool useChache,deferred,optimize,finalRender; int bpp;
  void setDefaults() {
    useChache=true;
    useChache=true, 
    deferred=false;
    optimize=true;
    finalRender=false;
    bpp=8;
  }
  RenderGlyphsOptions() { setDefaults(); }
};
void RenderGlyphs(Glyphs glyphs, RenderGlyphsOptions *options=0);
...
RenderGlyphsOptions opts[1];
opts->deferred=true;
RenderGlyphs(glyphs,opts);
...

Более того никто не запрещает писать так:
bool useCache=true, optimize=true;
RenderGlyphsDeferred(glyphs,useCache,optimize);

И вообще вместо функции с кучей параметров, которые потом еще и будут дополняться, можно объявить вспомогательный класс
struct RenderGlyphs {
  bool useChache,deferred,optimize,finalRender; int bpp;
  void setDefaults();
  RenderGlyphs() { setDefaults(); }
  void operator() (Glyphs glyphs) {}
};
...
RenderGlyphs render;
render.optimize=false;
render(glyphs);
Более того никто не запрещает писать так:
bool useCache=true, optimize=true;
RenderGlyphsDeferred(glyphs,useCache,optimize);

Так в этом и проблема, никто не запрещает написать
bool optimize=true, useCache=true;
RenderGlyphsDeferred(glyphs,optimize,useCache);
И ошибку увидеть практически невозможно в большой функции.

Вариант с массивом длины 1 — эстетически ужасен. Но просто структура с необязательными параметрами — ок.

Вариант с переменными ничем не лучше комментария /*useCache=*/

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

UFO just landed and posted this here

Хочу заметить, часть удобств при работе действительно обеспечивается IDE в определенных деталях, которые мы упускаем из виду. Вот как раз мне вспомнился пример, применительный к статье: в визуал студии в сишарпе очень выгодно использовать перечисления вместо флагов благодаря фиче интеллисенса: как только ты переходишь к указанию аргумента перечисления, тип перечисления сразу подставляется в списке, и ты сразу же видишь все варианты. А вот в интеллисенсе c++ той же визуал студии почему-то так не работает: приходится помнить название конкретного энума (почему-то оно само интеллисенсом не подставляется), и тут уже использование перечислений в качестве флагов становится чуть менее удобно из-за необходимости переключать голову. Извиняюсь за сумбур, на словах такое сложно объяснить

UFO just landed and posted this here

Доброго времени суток!

Простите за любопытстсво, а что мешает (начиная с С++17) использоват декомпозицию при объявлении (structural bindings)? Получается этакий "трюк", позволяющий уйти от FOR, снизить вероятность появления магических чисел на данном участке кода, а также позволяет не ломать голову над новыми названиями. Поправьте, если ошибаюсь.

struct RenderGlyphsParam
{
    bool useCache = true;
    bool deferred = false;
    bool optimize = true;
    bool finalRender = false;
};

// main
RenderGlyphsParam structured;
auto [useCache, deferred, optimize, finalRender] = structured;

// вызов:
RenderGlyphs(glyphs, useCache, deferred, optimize, finalRender);
UFO just landed and posted this here

Как вариант. Можно использовать enum, а не enum class. Тогда можно будет писать обычный код:


enum MyBool : bool
{
    True = true, False = false,
};

enum NotMyBool : bool
{
    True = true, False = false,
};

void printBool(MyBool b) {
    if (b) {
        cout << "myTrue" << endl;
    } else {
        cout << "myFalse" << endl;
    }
}

Выглядит, конечно, не очень красиво, но лучше чем с enum class и при этом в printBool мы уже не сможем отправить NotMyBool — компилятор скажет об ошибке.

enum MyBool : bool
{
    True = true, False = false,
};

enum NotMyBool : bool
{
    True = true, False = false,
};

Не компилируется, ошибка
1.cpp(8): error C2365: 'True': redefinition; previous definition was 'enumerator'
1.cpp(3): note: see declaration of 'True'
1.cpp(8): error C2365: 'False': redefinition; previous definition was 'enumerator'
1.cpp(3): note: see declaration of 'False'

Вы правы, чтобы не было redefinition необходимо их обернуть в namespace


namespace MyBool {

enum MyBool : bool
{
    True = true, False = false,
};

}

namespace NotMyBool {

enum NotMyBool : bool
{
    True = true, False = false,
};

}

Забыл об этом указать. Поэтому это и не очень красивый вариант

И интересный момент с помощью препроцессора эту портянку можно упростить используя этот макрос:


#define NAMED_BOOL(name) \
namespace _##name { \
enum name : bool \
{ \
    True = true, False = false, \
}; \
} \
using name = _##name::name ;

Пример использования:


#include <iostream>

using namespace std;

#define NAMED_BOOL(name) \
namespace _##name { \
enum name : bool \
{ \
    True = true, False = false, \
}; \
} \
using name = _##name::name ;

NAMED_BOOL(MyBool)
NAMED_BOOL(NotMyBool)

void printBool(MyBool a, NotMyBool b) {
    if (a && b) {
        cout << "myTrue" << endl;
    } else {
        cout << "myFalse" << endl;
    }
}

int main() {
    printBool(MyBool::True, NotMyBool::True); // compiled
    printBool(NotMyBool::True, MyBool::False); // not compiled
}

И это решение больше всего похоже на решение проблемы статьи.


Мы не теряем в удобстве написания функции, тк работает он по прежнему как bool и получаем безопасность при вызове функций с булевыми переменными.


А, и, кажется, должно работать даже для самых лохматых стандартов (с поправкой using на typedef) в том числе и в компайлтайм.

И чем это будет лучше enum class?

Взгляните на код `printBool` чуть выше https://habr.com/ru/company/skillfactory/blog/654253/#comment_24141209 . В отличие от enum class обычный enum не требует статик кастов в bool.

Работая с такими переменными вы пишите код внутри функции так будто это обычные булевы переменные, но получая все преимущества при вызове функции с булевыми значениями.

Не придётся писать вот такой код или использовать tagged_bool (если по каким-то причинам он вам не подходит)

if (mybool == MyBool::True)
или
if ((bool)mybool)

А что плохого в сравнении с MyBool::True? Особенно если вместо MyBool написать CacheOption, а вместо True — какой-нибудь Cached?

Ничего плохого нет, но это уже будет совсем другой подход и вообще не булево значение.

Лично мне интересно проработать именно булево значение. Понятно, что true/false не способны покрыть все возможные варианты развития архитектуры проекта, но я и не ставил такую цель, просто мне не понравилась необходимость сравнения с EnumClass::True.

А сравнение с MyBool::True просто выглядит излишне сложным.

Вы же не пишите в большинстве ситуаций:

if (isBool == true)

Любой человек на ревью вам скажет, что это бессмысленно и попросит написать так:

if (isBool)

Ох и наворотили) В c++ есть еще способ, сам пользуюсь, через структуру с функциями получается супер удобно. такой подход в tensorflow юзается))

struct RenderGlyphsParam {
    bool useCache = true;
    bool deferred = false;
    bool optimize = true;
    bool finalRender = false;

    RenderGlyphsParam& SetUseCache(bool value) {
      useCache = value;
			return *this;
    }
		//..
};
RenderGlyphs(glyphs, RenderGlyphsParam().SetUseCache(true).SetDeferred(false) 
   /*и т.д.*/);

классно, но замахаешься писать писать все эти бойлерплейт методы типа SetUseCache

Можно чуть сократить, вместо
 .SetUseCache(true).SetDeferred(false)
писать
 .Cached().NotDeferred()

А можно делать старые С перечисления,в которых названия значений сами себя документируют, типа USE_CACHE, NOT_USE_CACHE, и автоматическая конверсия к bool будет все делать сама, можно будет в коде и так и сяк использовать.

для тех кто из плюсов уже ничего не помнит - до сих пор нет именованных параметров функций в языке?

Потому что они нахрен не нужны...

В подавляющем числе случаев параметр bool в сигнатуре функции это бесполезная хрень которая может быть переписана в 2 функции типа Foo(bool IsCopy) преобразуется Foo() + FooCopy()

Получаем лучшую производительность, лучшую читаемость кода и понятнее становится реализация.

Если булева флажка больше чем 1, то проблема уже в другом месте(примерно том где это проектировалось)

А в реализации FooCopy у вас будет что, копипаста?

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

По такой логике, можно вообще все функции, принимающие короткие enum-ы, дробить по числу вариантов enum-а.
enum ColorChannel { Red, Green, Blue };
void DimChannel(Bitmap& target, ColorChannel ch);

надо превратить в три функции
void DimRedChannel(Bitmap& target);
void DimGreenChannel(Bitmap& target);
void DimBlueChannel(Bitmap& target);
А что, понятнее же становится :)

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

Те же самые опции, типа «useCache» или «optimize», запросто могут быть галочками в UI, и вот мы уже на ровном месте лепим ненужные ветвления.
Sign up to leave a comment.