<div dir="ltr">First off, this patch is far from ready for committing - it does not include<br>enough tests - includes commented out code - and even some dead<br>code. I shall try to address all those issues in the future - but for now<br>
I just wanted to put this out there for some early feedback (before I go<br> any deeper down a rabbit hole I need not have crawled into)<br><br>The patch implements the following (an incomplete list):<br> - converting auto parameters to template parameters and creating a<br>
function call operator template<br> - It does this by replacing the typesourceinfo of the parameter -<br> Should I avoid doing this? Is there a way to transform the auto<br> into a template type parameter, so that it subsequently behaves<br>
as a template type parameter, but remembers that it was<br> once an auto (for error messages only, right?)<br> - it makes TransformLambdaExpr and InstantiateFunctionDefinition<br> generic lambda aware (to allow for transformation of nested lambdas)<br>
so that the <br> - Regarding capturing for generic lambdas:<br> 1) A generic lambda (nested or not) will only have its captures<br> computed/finalized when its enclosing context is non-dependent <br> (and if an empty capture list, then no captures).<br>
2) When a generic lambda is invoked (i.e a specialization is instantiated) <br> any variable from an outer scope that is odr-used, but not already <br> captured by the lambda expression, results in an arror. <br>
3) If an outer lambda that is enclosed in a non-dependent context,<br> can't tell whether an inner lambda (with an implicit capture) <br> or a tree of inner lambdas (all with implicit captures) <br> might need to capture the variable for some<br>
instantiation of that inner lambda, the outer lambda will<br> capture that variable (if it has a capture default).<br> - this is implemented by hooking into ActOnCallExpr<br> (is it enough to assume that the only cases where an inner<br>
lambda might potentially need to capture a variable is <br> if it is used in a dependent function call (ctor, operator) <br> of the inner lambda, in a non-unevaluated context?)<br> - Within ActOnCallExpr, I dig into each argument, and as<br>
long as they are not in an unevaluated context (currently<br> I only check for sizeof), and are from a scope outside<br> the outer lambda, I capture the variable within the outer <br> lambda (if both the inner and outer lambda have a cap default,<br>
and the outer lambda is enclosed by a non-dependent context).<br> - The reason I do this in ActOnCallExpr is because until then<br> I do not know if the function call is still dependent, perhaps<br>
there is a better place to do this?<br> <br> I will include some code to illustrate various scenarios:<br> <br> #define L_CAP <br> #define M_CAP <br> #define N_CAP <br>
#define L_CALL <br> #define M_CALL<br> #define N_CALL<br> #define M_IS_GENERIC_IF_AUTO auto<br> #define TEST_CALL test('a') <br> <br> #define F_CALL f(x, selector)<br>
<br> void f(int, const int (&)[1]) { } <br> void f(const int&, const int (&)[2]) { } <br><br> void g(int, const int (&)[1]) { } <br> void g(int, const int (&)[2]) { } <br>
<br> template<class T><br> void test(T t) {<br> const int x = 0; <br> const int y = 10;<br> auto L = [L_CAP](auto a) -> void {<br> int selector[sizeof(t) == 1 ? 1 : 2]{};<br>
F_CALL;<br> auto M = [M_CAP](M_IS_GENERIC_IF_AUTO b) -> void {<br> int selector[sizeof(a) == 1 ? 1 : 2]{};<br> F_CALL;<br> auto N = [N_CAP](auto c) -> void {<br> int selector[sizeof(c) == 1 ? <br>
(sizeof(b) == 1 ? 1 : 2) : 2]{};<br> F_CALL; <br> }; <br> N_CALL; <br> };<br> M_CALL;<br> };<br> L_CALL; <br>
}<br><br> int main() {<br> TEST_CALL;<br> }<br> <br> Now lets consider the following scenarios (if the #define is not <br> mentioned in that scenario, it is as above):<br>
A) #define TEST_CALL test('h'); test(5); <br> - no errors <br> B) #define TEST_CALL test(5)<br> - no errors <br> - error if L is instantiated: e.g. #define L_CALL L('5')<br>
- ok again if: #define L_CAP =<br> C) #define TEST_CALL test(5) <br> #define L_CAP =<br> #define L_CALL L('j')<br> - no errors since M does not need to capture x<br>
- if #define N_CAP =, still no error.<br> - error if M is instantiated: e.g. #define M_CALL M('a')<br> - since when N is examined, it contains an expression that<br> depends on a dependent generic parameter, so therefore<br>
M contains an expression that depends on a dependent generic<br> parameter, and so x needs to be captured by M<br> but M has no default capture or explicit x capure.<br> - this can be fixed by #define M_CAP = OR #define M_CAP x<br>
- if #define N_CAP = <br> #define M_CALL M('a')<br> #define F_CALL sizeof(f(x, selector))<br> Then no error, since x can not be odr-used<br> - if #define N_CAP = <br>
#define M_CALL M('a') <br> #define F_CALL g(y, selector)<br> Even though all overloads of g don't require N to <br> capture y, y will need to be captured preemptively by M<br>
(which will error, unless M has a default capture or explicitly<br> captures y) but never by N, because when M has to finalize its<br> captures, it can only see that N uses x in a <br>
dependent expression, and so it can't know that all <br> instantiations of N will never odr-use y.<br> (although i guess it can by looking at each overload<br> and knowing that ADL is disabled for primitives and<br>
recognizing that every call requires the lvalue-to-rvalue<br> conversion, but this could get very hairy i imagine)<br> D) #define TEST_CALL test('h')<br> #define L_CALL L('j')<br>
#define M_IS_GENERIC_IF_AUTO char<br> - no errors <br> - if #define N_CALL N('k') - still no errors<br> - if #define N_CALL N(5) - error, N can not capture<br> - if #define M_IS_GENERIC_IF_AUTO auto<br>
and #define M_CALL M('k')<br> and #define N_CALL N('t') - still no errors<br> but if instead above #define M_CALL M(5) - error N cant capture x<br> <br> 4) Does ActOnCallExpr, cover dependent Constructor and Operator <br>
invocations? (sorry i'm being lazy here)<br> <br> <br>Additionally, It does not implement the following (but i will get to it):<br> - return type deduction for generic lambdas<br>
- conversion operator for generic lambdas with no captures<br> - comprehensive capturing of variadic packs within nested lambdas<br><br>Recognizing that this is a very rough draft, and that the code does need <br>significant refactoring, curating, testing and potential re-architecting<br>
before it is ready for commit; <br>I'm hoping to solicit the following feedback:<br> - general thoughts & discussion regarding the implementation strategy <br> (and whether there are better ways to do what I am doing). <br>
- on the correctness of the capturing semantics as described above and<br> their concordance with our standard wording.<br> - should i try and break up the patch into smaller patches when<br> submitting for actual commit?<br>
- should i continue working on this ...<br><br>Any constructive feedback will be welcome!<br><br>Thanks.<br><br><br><br> <br> <br><br></div>