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

Make match optional inside examine #6670

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

sellout
Copy link
Contributor

@sellout sellout commented May 17, 2023

This doesn’t necessitate any changes to examine call sites, but the match
wrapper around the lambdas can now be elided.

This is useful down the road if we use clang-format or something else where
specifying custom pretty-printing for individual functions is difficult.

What now looks like

examine(meh, match {
    [](const Foo& foo) { … },
    [](const Bar& bar) { … },
});

would, under clang-format, likely end up similar to

examine(
    meh,
    match {
        [](const Foo& foo) { … },
        [](const Bar& bar) { … },
    });

(which takes two extra lines, plus an additional indentation) but now can
instead be

examine(
    meh,
    [](const Foo& foo) { … },
    [](const Bar& bar) { … });

which fits in the same space, with the same indentation as our current approach.

@sellout sellout added C-cleanup Category: PRs that clean code up or issues documenting cleanup. S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. safe-to-build Used to send PR to prod CI environment labels May 17, 2023
@sellout sellout requested review from daira, nuttycom and str4d May 17, 2023 03:21
@ECC-CI ECC-CI removed the safe-to-build Used to send PR to prod CI environment label May 17, 2023
@sellout
Copy link
Contributor Author

sellout commented May 17, 2023

Review guidance: split view

This doesn’t necessitate any changes to `examine` call sites, but the `match`
wrapper around the lambdas can now be elided.

This is useful down the road if we use `clang-format` or something else where
specifying custom pretty-printing for individual functions is difficult.

What now looks like

    examine(meh, match {
        [](const Foo& foo) { … },
        [](const Bar& bar) { … },
    });

would, under `clang-format`, likely end up similar to

    examine(
        meh,
        match {
            [](const Foo& foo) { … },
            [](const Bar& bar) { … },
        });

(which takes two extra lines, plus an additional indentation) but now can
instead be

    examine(
        meh,
        [](const Foo& foo) { … },
        [](const Bar& bar) { … });

which fits in the same space, with the same indentation as our current approach.
///
/// The return type is inferred as it would be for `std::visit`.
template<class Specimen, class... Visitors>
constexpr decltype(auto) examine(Specimen&& specimen, Visitors&&... visitors) {
Copy link
Contributor

@daira daira May 19, 2023

Choose a reason for hiding this comment

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

Suggested change
constexpr decltype(auto) examine(Specimen&& specimen, Visitors&&... visitors) {
decltype(auto) examine(Specimen&& specimen, Visitors&&... visitors) {

This function doesn't meet the requirements for constexpr functions, specifically that "its return value (if any) and each of its parameters must be of a LiteralType".

(std::visit also doesn't meet that requirement and is constexpr, but maybe it's magic.)

This is UB when reading the spec strictly, even though a good compiler is likely to either just ignore the constexpr or do the right thing (which would be to make it constexpr only if it can be).

Copy link
Contributor

@daira daira May 19, 2023

Choose a reason for hiding this comment

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

Oh, I missed this part:

For constexpr function templates and constexpr member functions of class templates, at least one specialization must satisfy the abovementioned requirements. Other specializations are still considered as constexpr, even though a call to such a function cannot appear in a constant expression. If no specialization of the template would satisfy the requirements for a constexpr function when considered as a non-template function, the template is ill-formed, no diagnostic required. (until C++23)

It is unclear whether the last sentence means "If no possible specialization" (which would be okay as-is), or "If no actually instantiated specialization" — in which case we'd have to include an example like this:

static inline void examine_can_be_constexpr() {
    static_assert(42 == examine(42, [](int x) constexpr { return x; }));
}

It makes very little sense that you would need to do that, but this is C++, ¯\_(ツ)_/¯.

Copy link
Contributor

@daira daira May 19, 2023

Choose a reason for hiding this comment

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

@str4d, @nuttycom and I talked this through and convinced ourselves that:

  • the intended interpretation is probably "If no possible specialization";
  • but, it is a good idea to include the above example function anyway, since it acts as a static test that the constexpr usage works.

Copy link
Contributor

@daira daira May 19, 2023

Choose a reason for hiding this comment

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

@str4d also pointed out that match would be a better name for examine as of this PR. match would then be renamed to something else, say matcher. (Outside this file, currently there is one nontrivial "bare" use of match at wallet/rpcwallet.cpp line 585, and one other at wallet/wallet_tx_builder.cpp line 35 that is replaced in this PR.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

overloaded is the name used in the example we copied it from. I don’t think it’s a good name, but I think removing the only remaining explicit match from rpcwallet.cpp should be easy, so then the name is pretty internal.

Copy link
Contributor

Choose a reason for hiding this comment

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

If you look at the example in rpcwallet.cpp:

auto GetSourceForDestination = match {
[&](const CKeyID& addr) -> std::optional<PaymentAddressSource> {
return GetSourceForPaymentAddress(pwalletMain)(addr);
},
[&](const CScriptID& addr) -> std::optional<PaymentAddressSource> {
return GetSourceForPaymentAddress(pwalletMain)(addr);
},
[&](const CNoDestination& addr) -> std::optional<PaymentAddressSource> {
return std::nullopt;
},
};

the name matcher makes a lot of sense (more than overloaded). It's basically just treating the set of match clauses as a first-class object. And I don't think we need to discourage this construct from being used directly; it's sometimes the more natural approach to avoid code duplication.

Comment on lines +35 to +36
examine(
payment.address,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
examine(
payment.address,
examine(payment.address,

}
}, payment.address);
[&](const libzcash::SaplingPaymentAddress&) { ++saplingOutputCount; },
[&](const libzcash::OrchardRawAddress&) { ++orchardOutputCount; });
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
[&](const libzcash::OrchardRawAddress&) { ++orchardOutputCount; });
[&](const libzcash::OrchardRawAddress&) { ++orchardOutputCount; }
);

and similarly for the other uses.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It sounds like this was discussed in Ancient Issues today, so maybe we’re all on the same page already, but I wanted to check before I made all the changes. So, here’s a bit of summary, mostly for @nuttycom, @ebfull, and @str4d to respond to (and for @daira to call me out if I misrepresent something):

First, we can either get clang-format to approximate what we want, and get autoformatting, or add match (née examine) to WhitespaceSensitiveMacros (WSM) which effectively treats every call as if it were wrapped in clang-format off.

There are other *Macros lists (like ForEachMacros) that are less all-or-nothing than WSM, but I don’t think we can massage match into any of them.

Personally, I’m not that keen on WSM, especially for matches that don’t consist of 1-lineish lambdas – I like things being formatted for me. But, I’d much rather give up those sections of code formatting than lose them all. I also don’t really like the ) being treated like a brace in this one case, especially without the trailing ,. So, my own preferences are

  1. let clang-format do what it wants
    match(
        meh,
        [](const Foo& foo) { … },
        [](const Bar& bar) { … });
  2. figure out how to make a block macro for this (which I don’t think can work with the structure of this operation and CPP, but I’m happy to be proven wrong), like
    match(meh) {
        [](const Foo& foo) { … },
        [](const Bar& bar) { … },
    }
  3. manually do what we want here, using WSM
    match(meh,
        [](const Foo& foo) { … },
        [](const Bar& bar) { … }
    );

But, none of that is very strong. I just want some consensus before I make the edits.

Copy link
Contributor

@str4d str4d May 23, 2023

Choose a reason for hiding this comment

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

I am personally fine with 1, because 3 doesn't avoid the need to edit the last match alternative if we add another at the bottom. Also, reformatting later from any of these to the others won't touch any of the internal non-last match alternatives (except for moving away from 3 where maybe some internal whitespace changes), so I don't see that blocking later changes if we e.g. figure out how to do 2.

Copy link
Contributor

@daira daira left a comment

Choose a reason for hiding this comment

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

Please either remove the constexpr or explain why it's ok.

Please add the suggested examine_can_constexpr function, and consider whether to do the suggested renaming of examine to match and match to matcher (or some other name).

/// The return type is inferred as it would be for `std::visit`.
template<class Specimen, class... Visitors>
constexpr decltype(auto) examine(Specimen&& specimen, Visitors&&... visitors) {
return std::visit(match { visitors... }, specimen);
}

Copy link
Contributor

@daira daira May 19, 2023

Choose a reason for hiding this comment

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

Suggested change
// The C++ spec says that for a template function to be declared `constexpr`,
// it must be possible for "at least one specialization" to satisfy the
// `constexpr` requirements (which are documented at
// https://en.cppreference.com/w/cpp/language/constexpr#constexpr_function ).
// This example proves that this is possible.
static inline void examine_can_be_constexpr() {
static_assert(42 == examine(42, [](int x) constexpr { return x; }),
"constexpr examine should work");
}

@daira
Copy link
Contributor

daira commented May 19, 2023

Consider also updating .clang-format to include examine (or match if we make that change) as a "whitespace-sensitive macro".

Also converts one straggling `std::visit` to `examine`.
@sellout
Copy link
Contributor Author

sellout commented May 19, 2023

Consider also updating .clang-format to include examine (or match if we make that change) as a "whitespace-sensitive macro".

This is in #6678 – the current .clang-format is not useful for us, so we shouldn’t worry about updating it before something like #6678 is there. I only discovered it existed when I started using LSP again and my editor kept applying the incorrect .clang-format to my code as I wrote it.

@sellout sellout marked this pull request as draft May 23, 2023 16:13
@daira daira self-assigned this Jun 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-cleanup Category: PRs that clean code up or issues documenting cleanup. S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants