Комментарии 35
Есть некоторое ощущение , что если у вас 5 флагов в сигнатуре метода, то вы накосячили сильно раньше и исправлять надо явно не параметры.
Для нас в похожем случае лучшим выходом было изменение имени функции, по типу:
bool MatchCtrlAltShift(bool ctrl, bool alt, bool shift);
и никаких проблем ни с чтением ни написанием.
и никаких проблем ни с чтением ни написанием.
Это будет работать в IDE, при наведении курсора на функцию будет всплывать подсказка. В том-же блокноте код будет выглядеть
vall = MatchCtrlAltShift(true, true, false);
Я думаю что средствами языка эту проблему решить невозможно. Там всё очень хрупкое, одно неверное движение, и тут-же появится толпы невинно обиженных. Но есть вариант решения в виде плагина. Всплывающая подсказка в любой ide - как самое простое решение начального уровня. Нужно просто расширить полномочия - чтобы просмоторщик имел право менять текст на экране. Дополнять используемые функции именами переменных, есно другим цветом. Есно в режиме наложения изображения - для исключения ошибок копировать/вставить.
Подобный экспериментальный плагин просмоторщика уже делали для С::B. И столкнулись с огромной кучей проблем. Чтоб плагин работал корректно - нужно переписать не только С::B, но и фреймворки из которых он создаётся. То-есть там изначально нет места для маневрирования. А в других IDE всё намного печальнее - там нужно начинать с чистого листа.
Однако написать новый плагин, а может и IDE для него - гораздо дешевле чем делать очередную ветку развития в языках программирования, коих и так слишком много расплодилось.
Всё написанное касается изменённого отображения параметров функций.
Удивительно, но такая фича уже существует, причем не вчера появилась. Называется inlay hints.
Выглядит плюс-минус так:
IntelliJ
![](https://habrastorage.org/getpro/habr/upload_files/56f/154/0bd/56f1540bd61ef5f30dc79ee4b17342bb.png)
VS Code
![](https://habrastorage.org/getpro/habr/upload_files/5e0/133/42a/5e013342a4017b3bd6b49811439f1a6a.png)
Это будет работать в 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);
У идеи есть свои минусы, но право на жизнь тоже имеет.
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=*/
Последний вариант интересен, но если функция — уже сама по себе метод класса, то все становится заметно неприятнее.
Хочу заметить, часть удобств при работе действительно обеспечивается IDE в определенных деталях, которые мы упускаем из виду. Вот как раз мне вспомнился пример, применительный к статье: в визуал студии в сишарпе очень выгодно использовать перечисления вместо флагов благодаря фиче интеллисенса: как только ты переходишь к указанию аргумента перечисления, тип перечисления сразу подставляется в списке, и ты сразу же видишь все варианты. А вот в интеллисенсе c++ той же визуал студии почему-то так не работает: приходится помнить название конкретного энума (почему-то оно само интеллисенсом не подставляется), и тут уже использование перечислений в качестве флагов становится чуть менее удобно из-за необходимости переключать голову. Извиняюсь за сумбур, на словах такое сложно объяснить
Доброго времени суток!
Простите за любопытстсво, а что мешает (начиная с С++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);
Как вариант. Можно использовать 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)
/*и т.д.*/);
А можно делать старые С перечисления,в которых названия значений сами себя документируют, типа USE_CACHE, NOT_USE_CACHE, и автоматическая конверсия к bool будет все делать сама, можно будет в коде и так и сяк использовать.
для тех кто из плюсов уже ничего не помнит - до сих пор нет именованных параметров функций в языке?
В подавляющем числе случаев параметр bool в сигнатуре функции это бесполезная хрень которая может быть переписана в 2 функции типа Foo(bool IsCopy) преобразуется Foo() + FooCopy()
Получаем лучшую производительность, лучшую читаемость кода и понятнее становится реализация.
Если булева флажка больше чем 1, то проблема уже в другом месте(примерно том где это проектировалось)
А в реализации FooCopy у вас будет что, копипаста?
во первых с точки зрения юзера интерфейса абсолютно неважно что там будет написано, просто так будет лучше. Во вторых там будет то что в другой ветке if в версии с bool параметром
enum ColorChannel { Red, Green, Blue };
void DimChannel(Bitmap& target, ColorChannel ch);
надо превратить в три функции
void DimRedChannel(Bitmap& target);
void DimGreenChannel(Bitmap& target);
void DimBlueChannel(Bitmap& target);
А что, понятнее же становится :)да, именно так и нужно сделать... Не сказать что это вообще частый сценарий и редко нельзя сделать шаблон с аргументом этого енама(то есть нужно на рантайме узнавать)
Способы переписать логические параметры в С++