Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace use of TriStateBool with boost::optional. #7906

Closed
wants to merge 15 commits into from

Conversation

tgregerson
Copy link
Contributor

boost::optional (and eventually std::optional in C++17) provide a generalized mechanism for specifying tristate variables of any type.

I discovered I needed tristate variables of different types than bool while scoping out the work for #5201 / #5779 so it made sense to do this cleanup first.

boost::optional (and std::optional in C++17) provide a more general
mechanism for specifying tri-state values that works for types other
than bool.
@tgregerson
Copy link
Contributor Author

Seems like this Travis build does not work with boost features from 4/2014? What is the minimum boost version qbt is supposed to support?

older "get" methods. These are mostly functionally equivalent, and are
supported on older versions of Boost.
@Chocobo1
Copy link
Member

http://www.boost.org/doc/libs/1_65_1/libs/optional/doc/html/boost_optional/tutorial/a_note_about_optional_bool_.html
What about using boost::tribool? I surveyed a while ago and find it incompatible (when doing comparisons), maybe you can work it out.

@tgregerson
Copy link
Contributor Author

http://www.boost.org/doc/libs/1_65_1/libs/optional/doc/html/boost_optional/tutorial/a_note_about_optional_bool_.html
What about using boost::tribool? I surveyed a while ago and find it incompatible (when doing comparisons), maybe you can work it out.

I read that note and considered boost::tribool at the onset, but I think that optional is a better fit.

  • As the author of that boost note mentions, tribool treats "indeterminate" as a third valid state, whereas optional treats "none" as an invalid state indicating a missing value. The latter semantics are what we want. We are using them to indicate cases where the user has not provided a value.
  • I was motivated to write this PR when changing some torrent creation parameters with non-bool types (e.g. int, double) over to being boost::optional. Having a single API for dealing with optional parameters made the code cleaner than having one API for bools and a completely different API for all other types.

@Chocobo1
Copy link
Member

Chocobo1 commented Dec 6, 2017

What is the minimum boost version qbt is supposed to support?

IIRC version 1.35 is defined in configure.ac, what minimum boost version is in line with the namings in std::optional?

else if (skipDialog == TriStateBool::False) {
result.append(QLatin1String("@skipDialog=0"));
if (skipDialog) {
if (skipDialog.get())
Copy link
Member

@Chocobo1 Chocobo1 Dec 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO you should decide on one: operator* or value(), I saw both are used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't use value() since it requires a newer version of boost. Probably better to use operator* then, since get() is deprecated.

default: return QJsonValue();
}
if (opt)
return QJsonValue(*opt);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return *opt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
if (opt)
return QJsonValue(*opt);
return QJsonValue();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

personal preference:

if (!opt)
    return QJsonValue();
return *opt;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
return ret;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditch ret and inline the returns.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

ui->startTorrentCheckBox->setChecked(true);
else
ui->startTorrentCheckBox->setChecked(!session->isAddTorrentPaused());
ui->startTorrentCheckBox->setChecked(m_torrentParams.addPaused.get_value_or(!session->isAddTorrentPaused()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong translation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, thanks

else if (m_currentRule.addPaused() == TriStateBool::False)
index = 2;
if (m_currentRule.addPaused()) {
if (m_currentRule.addPaused().get())
Copy link
Member

@Chocobo1 Chocobo1 Dec 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use optional::operator==:

if (m_currentRule.addPaused() == true)
    index = 1;
else
    index = 2;

at least std::optional works like it, not sure about boost:optional

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately not supported

}
else if (addPaused == TriStateBool::False) {
result.append(QLatin1String("@addPaused=0"));
if (addPaused) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use operator==

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no operator== (or any comparators) in boost's version of optional.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to this gotcha you can do:

if (addPaused == true)
    result.append(QLatin1String("@addPaused=1"));
else if (addPaused == false)
    result.append(QLatin1String("@addPaused=0"));

Its simpler. But will it confuse people? Maybe.
I don't have strong preference either way. Do you guys have any argument for or against one or the other?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer @sledgehammer999 variant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
else if (skipDialog == TriStateBool::False) {
result.append(QLatin1String("@skipDialog=0"));
if (skipDialog) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use operator==

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above

@tgregerson
Copy link
Contributor Author

IIRC version 1.35 is defined in configure.ac, what minimum boost version is in line with the namings in std::optional?

value() and value_or() require Boost 1.56.

operator* is supported in 1.35, so that can be used in place of value(). The only difference is in exception handling.

@Chocobo1
Copy link
Member

OK, you can squash the commits (I would squash all into the first one).

@Chocobo1
Copy link
Member

@sledgehammer999
Are you OK with bumping boost minimum version to 1.56?
boost 1.56 was released 3 years ago 2014/08/07

@LordNyriox
Copy link
Contributor

@Chocobo1:

Are you OK with bumping boost minimum version to 1.56?

IIRC qBittorrent cannot use Boost 1.56 (it was stuck at 1.55 for a while), due to a change in Boost.asio.

Perhaps use the first version to support the define workaround, instead?

boost::optional (and std::optional in C++17) provide a more general
mechanism for specifying tri-state values that works for types other
than bool.
@tgregerson
Copy link
Contributor Author

OK, you can squash the commits (I would squash all into the first one).

Done

Chocobo1
Chocobo1 previously approved these changes Dec 12, 2017
@Chocobo1
Copy link
Member

IIRC qBittorrent cannot use Boost 1.56 (it was stuck at 1.55 for a while), due to a change in Boost.asio.

I don't remember that... or you might be talking about #4112?
That issue is old, things should be different now.
This PR is good to merge, the boost version bump can be done in another PR.

@LordNyriox
Copy link
Contributor

LordNyriox commented Dec 15, 2017

@Chocobo1: No, I was referring to the issue that @sledgehammer999 solved using BOOST_ASIO_DISABLE_CONNECTEX.
That define wasn't added until Boost 1.58, but the connectex problem started in 1.56.

I do not recall if an alternative solution to this problem was discovered or not. If not, please make the minimum Boost version 1.58 rather than 1.56.

@Chocobo1
Copy link
Member

I do not recall if an alternative solution to this problem was discovered or not.

That issue is still open: arvidn/libtorrent#314 (comment)
But it's less relevant now, havn't seen a complain for some time.

Bumping to boost 1.58 which is released on 2015-04-17 (quite ancient) shouldn't pose a problem I guess.

}
return 0; // use default
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here you can safely do:

if (opt == true)
    return 1; // always
else if (opt == false)
    return 2; // never
else
    return 0; // use default

A reader might scratch his head at first but afterwards he'll realize what we do here. No?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the @sledgehammer999 variant (if it can be compiled) except the else's after return's.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @glassez means:

if (opt == true)
    return 1; // always
else if (opt == false)
    return 2; // never

return 0; // use default

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (opt == true)
    return 1; // always
if (opt == false)
    return 2; // never
return 0; // use default

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Member

@Chocobo1 Chocobo1 Jan 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I'm late to this, just want to point out it might be dangerous to use this form (in this specific case optional<bool>), one might transform it to if (opt) return 1; if (!opt) return 2; which is clearly not the same thing.
To avoid this pitfall, I personally like to see it as a container type, using operator* to access it, which is also why I didn't think of using operator== for comparing with values.

Just sharing my view point.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (opt) return 1;
if (!opt) return 2;

In my opinion, it looks "too ugly" to some sane developer want to write it like this.

In fact, this "extreme case" has forced me to abandon the use of any generic classes (boost::optional etc.). My TriStateBool doesn't allow any confusing implicit conversions.
But I don't want, of course, to prevent these changes because of their further spread to other fields. I just wish we have defined some common policy for its use.

I didn't think of using operator== for comparing with values.

But it's convenient, at least for types other than bool. Otherwise boost::optional becomes not much clear than using ordinary pointers.

Using if (optional) to check for value existence is convient but it's confusing in case of bool.

@sledgehammer999
Copy link
Member

Btw, IMO we can safely bump Boost requirement to 1.56. The problem @LordNyriox mentions is valid only for Windows. And I am not going to provide Windows builds based on that old version anyway.

I left 2 inline comments. They aren't merge-blocking.

Since TriStateBool was implemented by @glassez, let's tag him in case he hasn't seen this already.

@glassez
Copy link
Member

glassez commented Dec 24, 2017

I need to make all add torrent params optional (for my further features) but I considered it in another way. I thought to implement AddTorrentParams as QVariantHash. But it also can be implemented as struct of optionals. @sledgehammer999, if you prefer this way, let me known and then I'll review this PR.

@tgregerson
Copy link
Contributor Author

I need to make all add torrent params optional (for my further features) but I considered it in another way. I thought to implement AddTorrentParams as QVariantHash. But it also can be implemented as struct of optionals. @sledgehammer999, if you prefer this way, let me known and then I'll review this PR.

FWIW, I already have the conversion of the rest of the struct to optionals done in another PR that I haven't sent for review yet. I was waiting for this one to get merged before sending it out.

@sledgehammer999
Copy link
Member

@glassez since its done already here there's probably no reason to do it via QVariantHash.
So if you want proceed with review.

Copy link
Member

@glassez glassez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also shouldn't we care about easy transition to std::optional? Maybe declare aliases for both boost::optional and boost::none?

#include "../utils/fs.h"
#include "../utils/string.h"
#include "rss_feed.h"
#include "rss_article.h"

namespace
{
TriStateBool jsonValueToTriStateBool(const QJsonValue &jsonVal)
boost::optional<bool> jsonValueToOptional(const QJsonValue &jsonVal)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsonValueToOptionalBool or just jsonValueToBool

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (and elsewhere)

}

QJsonValue triStateBoolToJsonValue(const TriStateBool &triStateBool)
QJsonValue optionalToJsonValue(const boost::optional<bool> &opt)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optionalBoolToJsonValue or boolToJsonValue

}

TriStateBool addPausedLegacyToTriStateBool(int val)
boost::optional<bool> addPausedLegacyToOptional(int val)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same as above

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addPausedLegacyToOptionalBool, as I said already.

}
}

int triStateBoolToAddPausedLegacy(const TriStateBool &triStateBool)
int optionalToAddPausedLegacy(const boost::optional<bool> &opt)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same as above.

if (opt) {
if (*opt)
return 1; // always
else
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case else is redundant after return.

}
return 0; // use default
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the @sledgehammer999 variant (if it can be compiled) except the else's after return's.

@sledgehammer999
Copy link
Member

sledgehammer999 commented Dec 27, 2017

Also shouldn't we care about easy transition to std::optional? Maybe declare aliases for both boost::optional and boost::none?

Sounds good, but once we replace it with std::optional we would continue using a "useless/extra" typedef/alias. So I am in favor of having before our eyes the real type*.
*Unless we want to shorten it for readability.

@tgregerson
Copy link
Contributor Author

Also shouldn't we care about easy transition to std::optional? Maybe declare aliases for both boost::optional and boost::none?

I think it will probably be easier to just do a find & replace to swap types rather than having a header with aliases that needs to get included everywhere they are used and eventually deleted after the switch.

@LordNyriox
Copy link
Contributor

@tgregerson: Any chance you can rebase / update this PR?

It has been stagnant since February.

@glassez
Copy link
Member

glassez commented Oct 3, 2018

@LordNyriox, please don't ask people to do potentially useless work. These changes are still not generally approved and may never be approved. But if we want to merge it, we'll ask to rebase the code in due time.

@tgregerson
Copy link
Contributor Author

I'll reintegrate if the powers that be can come to a consensus that we should move forward with it.

As a side note, I would still like to implement the automatic category assignment feature I mentioned in the original comment on this PR, but I have not been able to make any progress since this first piece has been in limbo.

Copy link
Member

@glassez glassez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just re-read this topic and re-review the code. In principle, this is not bad in itself (even if we do not look at other scheduled changes). Since I don't see any blocking objections from other project members I think we can go forward with it once it is rebased and fixed.

@@ -426,7 +428,7 @@ void Application::processParams(const QStringList &params)
}

if (param.startsWith(QLatin1String("@addPaused="))) {
torrentParams.addPaused = param.mid(11).toInt() ? TriStateBool::True : TriStateBool::False;
torrentParams.addPaused = param.mid(11).toInt() != 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please wrap right part with parentheses.
And in assignment below too.

}

TriStateBool addPausedLegacyToTriStateBool(int val)
boost::optional<bool> addPausedLegacyToOptional(int val)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addPausedLegacyToOptionalBool, as I said already.

}
if (opt == true)
return 1; // always
else if (opt == false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use else after return (as well as after break, continue etc.).

@@ -366,12 +366,10 @@ QStringList QBtCommandLineParameters::paramList() const
if (!savePath.isEmpty())
result.append(QString("@savePath=%1").arg(savePath));

if (addPaused == TriStateBool::True) {
if (addPaused == true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, boost::optional<bool>::operator== returns false in both cases unless value is set, isn't it?

This reverts commit e2e6638, reversing
changes made to 75e1ece.
boost::optional (and std::optional in C++17) provide a more general
mechanism for specifying tri-state values that works for types other
than bool.

Replace use of the newer "value" methods from boost::optional with the
older "get" methods. These are mostly functionally equivalent, and are
supported on older versions of Boost.

boost::optional fixup

optional cmdoptions fixup

Address comments

Revert "Merge branch 'autopopulate' into master"

This reverts commit e2e6638, reversing
changes made to 75e1ece.
@tgregerson
Copy link
Contributor Author

I gave up on attempting to rebase. It would be easier to start from scratch than to try to resolve all the merge conflicts.

@tgregerson tgregerson closed this Oct 13, 2018
@LordNyriox
Copy link
Contributor

@tgregerson:

It would be easier to start from scratch

Will you do so?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants