On Mon, May 13, 2013 at 10:25 PM, Faisal Vali <span dir="ltr"><<a href="mailto:faisalv@gmail.com" target="_blank">faisalv@gmail.com</a>></span> wrote:<br><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<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></div></blockquote><div><br></div><div>Just a couple of thoughts below, I've not had time to study the patch itself yet.</div>
<div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr">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></div></blockquote><div><br></div><div>Perhaps an AutoType whose deduced type is the template parameter would work? However, note this comment from SubstituteAutoTransform:</div>
<div><br></div><div><div> // If we're building the type pattern to deduce against, don't wrap the</div><div> // substituted type in an AutoType. Certain template deduction rules</div><div> // apply only when a template type parameter appears directly (and not if</div>
<div> // the parameter is found through desugaring). For instance:</div><div> // auto &&lref = lvalue;</div><div> // must transform into "rvalue reference to T" not "rvalue reference to</div>
<div> // auto type deduced as T" in order for [temp.deduct.call]p3 to apply.</div></div><div><br></div><div>If we wanted that to work, we'd need to teach some parts of template deduction to walk over the AutoType node.</div>
<div><br></div><div>How significant is the diagnostic impact of performing the substitution? Where, and how often, does it show up?</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<div dir="ltr"> - 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></div>
</blockquote><div><br></div><div>Per the proposal, you need captures in other situations:</div><div><br></div><div>struct S {</div><div> constexpr S() {}</div><div> S(const S&) { puts("captured"); }</div><div>
};</div><div>void f() {</div><div> constexpr S s {};</div><div> [=] (auto x) {</div><div> // captures 's' (even though no instantiation can need it),</div><div> // because the full-expression depends on 'x'. Therefore</div>
<div> // we must print "captured".</div><div> (void)x, s;</div><div> };</div><div>}</div><div><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<div dir="ltr"> - 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?</div></blockquote><div><br></div><div>Here's how I was imagining this working when we were in CWG:</div><div><br></div><div>The captures for a lambda are not finalized until the outermost context of the reaching scope is non-dependent. At that point, for each call to ActOnFinishFullExpr within the lambda, we check whether the full-expression is instantiation-dependent. If it is, we scan it for entities which it might need to capture, and capture all of them. There may be ways of optimizing this (maybe keep a list of the potentially-captured variables for each full-expression as we build it).</div>
<div style="text-align:left"><u><br></u></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div style="text-align:left"> </div> 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>
</blockquote></div><br>