Skip to content

Conversation

dongcarl
Copy link
Contributor

Now that we're using C++17, we can use the decltype(auto) return type
for functions and lambda expressions.

As demonstrated in this commit, this can simplify cases where previously
the compiler failed to deduce the correct return type.

Just for reference, for the "assign to ref" cases fixed here, there are
3 possible solutions:

  • Return a pointer and immediately deref as used before this commit
  • Make sure the function/lambda returns declspec(auto) as used after
    this commit
  • Class& i = WITH_LOCK(..., return std::ref(...));

References:

  1. https://en.cppreference.com/w/cpp/language/function#Return_type_deduction
  2. https://en.cppreference.com/w/cpp/language/template_argument_deduction#Other_contexts
  3. https://en.cppreference.com/w/cpp/language/auto
  4. https://en.cppreference.com/w/cpp/language/decltype

Explanations:

  1. https://stackoverflow.com/a/21369192
  2. https://stackoverflow.com/a/21369170

Thanks to sipa and ryanofsky for helping me understand this

@DrahtBot DrahtBot added the Tests label Nov 25, 2020
@DrahtBot
Copy link
Contributor

DrahtBot commented Nov 26, 2020

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Conflicts

Reviewers, this pull request conflicts with the following ones:

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@jnewbery
Copy link
Contributor

Code review ACK 50dee5a

Thanks for the excellent explanation!

It's not important, but decltype(auto) has been available since c++14. You could mention that in the commit log if you felt like it.

Another really good reference on decltype(auto) is item 3 in Effective Modern C++ (Scott Meyers). That chapter explains one sharp edge that I wanted to test, namely that decltype(auto(i)) and decltype(auto((i))) are not always the same:

#include <iostream>
#include <mutex>

#define LOCK(cs) (void(cs))
#define WITH_LOCK(cs, code) [&]() -> decltype(auto) { LOCK(cs); code; }()

int main()
{
    std::mutex l;

    int i = WITH_LOCK(l, {int i = 1; return i;}); // this is fine, auto(decltype(i)) is an int, so this returns by value
    std::cout << "i = " << i << std::endl;

    int j = WITH_LOCK(l, {int j = 1; return (j);}); // this isn't, auto(decltype((i)) is an &int, so this returns by reference to a local variable
    std::cout << "j = " << j << std::endl; // segfault!! j is a dangling reference

    return 0;
}

This kind of bug is non-obvious from looking at the code, but it's detected by -Wreturn-local-addr, which I believe we have enabled by default:

g++ decltype_auto.cpp 
decltype_auto.cpp: In lambda function:
decltype_auto.cpp:14:46: warning: reference to local variable ‘j’ returned [-Wreturn-local-addr]
   14 |     int j = WITH_LOCK(l, {int j = 1; return (j);}); // this isn't, auto(decltype((i)) is an &int, so this returns by reference to a local variable
      |                                              ^

@ajtowns
Copy link
Contributor

ajtowns commented Dec 1, 2020

Is this actually clearer or less fragile (or better in some other way?) than having the explicit & and *?

@maflcko
Copy link
Member

maflcko commented Dec 2, 2020

I think the only benefit is when new code is written, it will less likely result in a compile error. So devs need to do the add-*-or&-at-random-places-until-it-compiles workaround less often.

@dongcarl dongcarl force-pushed the 2020-11-decltype-auto branch from 50dee5a to 7a8f4b9 Compare December 3, 2020 21:29
@dongcarl
Copy link
Contributor Author

dongcarl commented Dec 3, 2020

Is this actually clearer or less fragile (or better in some other way?) than having the explicit & and *?

My intuition could be wrong, but I think that most people, when calling WITH_LOCK, don't expect their deducted return type to be "coerced" into an object type. In WITH_LOCK's particular case, this seem somewhat rigid and affects the ergonomics of a major use of WITH_LOCK: making use of its return value.

@dongcarl
Copy link
Contributor Author

dongcarl commented Dec 3, 2020

Pushed 50dee5a to 7a8f4b9: put some more context into the repo. Thanks jnewbery!

Copy link
Contributor

@promag promag left a comment

Choose a reason for hiding this comment

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

Code review ACK 7a8f4b9, could squash.

@promag
Copy link
Contributor

promag commented Dec 3, 2020

@jnewbery I get a different warning:

$ clang++ -std=c++17 decltype_auto.cpp
decltype_auto.cpp:14:46: warning: reference to stack memory associated with local variable 'j' returned [-Wreturn-stack-address]
    int j = WITH_LOCK(l, {int j = 1; return (j);}); // this isn't, auto(decltype((i)) is an &int, so this returns by reference to a local variable
                                             ^
decltype_auto.cpp:5:65: note: expanded from macro 'WITH_LOCK'
#define WITH_LOCK(cs, code) [&]() -> decltype(auto) { LOCK(cs); code; }()
                                                                ^~~~
1 warning generated.

And doesn't segfault.

Copy link
Contributor

@jnewbery jnewbery left a comment

Choose a reason for hiding this comment

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

utACK 7a8f4b9.

I'm +/-0 on adding the comment about decltype, since I don't think we need to document every quirk of c++ in our codebase. I don't think it does any harm though.

@dongcarl dongcarl force-pushed the 2020-11-decltype-auto branch from 7a8f4b9 to 3216883 Compare December 4, 2020 17:09
Now that we're using C++17, we can use the decltype(auto) return type
(available since C++14) for functions and lambda expressions.

As demonstrated in this commit, this can simplify cases where previously
the compiler failed to deduce the correct return type.

Just for reference, for the "assign to ref" cases fixed here, there are
3 possible solutions:

- Return a pointer and immediately deref as used before this commit
- Make sure the function/lambda returns declspec(auto) as used after
  this commit
- Class& i = WITH_LOCK(..., return std::ref(...));

-----

References:
1. https://en.cppreference.com/w/cpp/language/function#Return_type_deduction
2. https://en.cppreference.com/w/cpp/language/template_argument_deduction#Other_contexts
3. https://en.cppreference.com/w/cpp/language/auto
4. https://en.cppreference.com/w/cpp/language/decltype

Explanations:
1. https://stackoverflow.com/a/21369192
2. https://stackoverflow.com/a/21369170
3. Item 3 in Effective Modern C++ (Scott Meyers) via jnewbery
@dongcarl dongcarl force-pushed the 2020-11-decltype-auto branch from 3216883 to 3eb94ec Compare December 4, 2020 17:23
Copy link
Member

@hebasto hebasto left a comment

Choose a reason for hiding this comment

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

ACK 3eb94ec, I have reviewed the code and it looks OK, I agree it can be merged. I have verified possible warnings:

  • -Wreturn-stack-address in clang
  • -Wreturn-local-addr in gcc

I think this change makes reading code which uses the WITH_LOCK macro and reasoning about it easier.

@jnewbery
Copy link
Contributor

jnewbery commented Dec 7, 2020

utACK 3eb94ec

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

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

Code review ACK 3eb94ec

@fanquake fanquake merged commit 7838db1 into bitcoin:master Jan 12, 2021
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Jan 12, 2021
3eb94ec sync: Use decltype(auto) return type for WITH_LOCK (Carl Dong)

Pull request description:

  > Now that we're using C++17, we can use the decltype(auto) return type
  > for functions and lambda expressions.
  >
  > As demonstrated in this commit, this can simplify cases where previously
  > the compiler failed to deduce the correct return type.
  >
  > Just for reference, for the "assign to ref" cases fixed here, there are
  > 3 possible solutions:
  >
  > - Return a pointer and immediately deref as used before this commit
  > - Make sure the function/lambda returns declspec(auto) as used after
  >   this commit
  > - Class& i = WITH_LOCK(..., return std::ref(...));
  >
  > -----
  >
  > References:
  > 1. https://en.cppreference.com/w/cpp/language/function#Return_type_deduction
  > 2. https://en.cppreference.com/w/cpp/language/template_argument_deduction#Other_contexts
  > 3. https://en.cppreference.com/w/cpp/language/auto
  > 4. https://en.cppreference.com/w/cpp/language/decltype
  >
  > Explanations:
  > 1. https://stackoverflow.com/a/21369192
  > 2. https://stackoverflow.com/a/21369170

  Thanks to sipa and ryanofsky for helping me understand this

ACKs for top commit:
  jnewbery:
    utACK 3eb94ec
  hebasto:
    ACK 3eb94ec, I have reviewed the code and it looks OK, I agree it can be merged. I have verified possible warnings:
  ryanofsky:
    Code review ACK 3eb94ec

Tree-SHA512: 5f55c7722aeca8ea70e5c1a8db93e93ba0e356e8967e7f607ada38003df4b153d73c29bd2cea8d7ec1344720d37d857ea7dbfd2a88da1d92e0e9cbb9abd287df
@maflcko
Copy link
Member

maflcko commented Jun 18, 2021

review ACK 3eb94ec

@bitcoin bitcoin locked as resolved and limited conversation to collaborators Aug 18, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants