Extract Function
August 30, 2018 ยท View on GitHub
{{< Production >}}
Extract Function
If you get to the end of step 1, the refactoring is possible - it will produce a valid result.
This recipe is designed to do Extract Function and nothing else. If you want something else, there are other refactorings for that.
You can commit at the end of any numbered step. At each such point, the code will compile and produce exactly the same results as the original code.
Constraints
This recipe only works on rvalue expressions or on whole blocks (surrounded by braces) or a single for/while/if statement. It does not work on single statements, due to variable lifetime changes. If you need to extract statements but not a block, first execute the Extract Block refactoring
Recipe
1. Introduce a lambda
For a block/for/while/if, surround with:
[&]() {
// ...
}();
For an expression, surround it with:
[&]() { return
// ...
;}()
Compile single file. Possible errors:
not all control paths return a value. You have an early return. Back up and either Eliminate Early Return/Continue/Break (fix link) or extract something different.a break/continue statement may only be used within .... You have a break/continue. Back up and either Eliminate Early Return/Continue/Break (fix link) or extract something different.
Search the new lambda for any return statements (using find). If there are any returns:
If it's obvious that all code paths return, then add a return before the lambda:
return [&]() {
//...
}();
If it's not obvious that all code paths return, then back up and either Eliminate Early Return/Continue/Break (fix link) or try something different.
2. Extract Variable
- Assign the lambda to
Applesauceand call it. (Extract Variable)
For example,
[&]() {
// ...
}();
becomes:
auto Applesauce = [&]() {
// ...
}; Applesauce();
Compile to make sure you didn't typo.
3. Set the return type
Set the return type on the lambda, even if it's void. In Visual Studio, the IntelliSense tooltip over auto will tell you the type.
auto Applesauce = [&]() -> SOMETYPE {
// ...
};
Compile to make sure you got the return type correct.
4. Capture explicitly
Replace [&] with [this] (or [] in a free function) and compile.
For each error about a variable that must be captured:
- Copy the variable name
- Paste it in to the capture list, prefixed with
&. - Repeat until green.
For example,
auto Applesauce = [this, &foo]() -> bool {
cout << foo;
};
The order of the capture list will influence the order of the parameters of the final function. If you want the parameters in a particular order, now is a good time to reorder the capture list.
5. Convert captures to parameters
- Select the capture list (but not 'this') and Cut
- Paste into the argument list, removing
&s. - Paste into the parameter list and inject
const autoin front of each parameter - Compile.
- If you get an error because the variable is modified, make it non-const
For example,
auto Applesauce = [this, &i, &s]() -> void
{
cout << ++i << s;
};
Applesauce();
becomes:
auto Applesauce = [this](auto &i, const auto &s) -> void
{
cout << ++i << s;
};
Applesauce(i, s);
6. Explicitly specify parameter types
For each argument:
- Go-to-definiton on the argument
- Copy the variable type
- Paste into the lambda parameter list
- Compile
For example,
int i = ...
std::string s = ...
auto Applesauce = [this](auto &i, const auto &s) -> void
{
std::cout << ++i << s;
};
Applesauce(i, s);
becomes:
int i = ...
std::string s = ...
auto Applesauce = [this](int &i, const std::string &s) -> void
{
std::cout << ++i << s;
};
Applesauce(i, s);
About pointer parameters and the const keyword
When the parameter is a pointer type, it's important to understand how the placement of the const keyword affects the parameter type.
For example, given a local variable in Column* pCol = ..., the original type is "Pointer to Column", so the pointer is what
must be const (so we know it's not reseated to point to another object). It's also important to specify that the Column itself is
const to avoid losing the implicit constness in the original code as we make the data flow explicit.
const Column* // WRONG - this is a non-const pointer to const column
Column const* // WRONG - same as above
Column* const // BETTER - this is a const pointer to non-const column
Column const * const // BEST - this is a const pointer to const column
To avoid ambiguity, always place the const keyword to the right of what you want to make const.
7. Try to eliminate this capture
- Remove
thisfrom the capture list - Compile
- If the compile fails, undo
For example,
auto Applesauce = [this]() -> void {
// ...
};
becomes:
auto Applesauce = []() -> void {
// ...
};
8. Convert lambda to function
- If
thisis captured, use 8A. - If
thisis not captured, use 8B.
8A. Convert this-bound lambda to member function
- Cut the lambda statement and paste it outside the current function.
- Remove
= [this]. - Mark the new method as
const. - Copy the signature line.
- Add
SomeClass:: - Paste the signature in to the class declaration in a private section.
- Compile and remove
constif necessary.
For example,
class SomeClass
{
private:
auto Applesauce () const -> void;
}
auto SomeClass::Applesauce () const -> void {
// ...
};
8B. Convert non-this Lambda to free function
- Cut the lambda statement and paste it above the current function.
- Remove
= [] - Wrap it in an anonymous namespace
- Compile and resolve any errors:
- If the free function uses typedefs/aliases or classes nested in the original class, convert the free function to a private static function of the original class.
For example,
namespace {
auto Applesauce () -> void {
...
};
}
9. Convert return value from lambda-like syntax to traditional syntax
- Select the return value (after the
->) and cut it. - Delete the
-> - Select the word
autoand paste the return value over it. - Remove the trailing semicolon.
For example,
namespace {
auto Applesauce () -> void {
...
};
}
becomes:
namespace {
void Applesauce () {
...
}
}