Skip to content

Commit

Permalink
Merge pull request #451 from andreasfertig/fixIssue425
Browse files Browse the repository at this point in the history
Fixed #425: Show the correct reference type for structured bindings.
  • Loading branch information
andreasfertig authored Feb 8, 2022
2 parents b9757bd + f317d9a commit 3fd1804
Show file tree
Hide file tree
Showing 17 changed files with 291 additions and 33 deletions.
16 changes: 9 additions & 7 deletions CodeGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3895,7 +3895,6 @@ void CodeGenerator::InsertArg(const BindingDecl*)

void StructuredBindingsCodeGenerator::InsertArg(const BindingDecl* stmt)
{
// Assume that we are looking at a builtin type. We have to construct the variable declaration information.
const auto* bindingStmt = stmt->getBinding();

// In a dependent context we have no binding and with that no type. Leave this as it is, we are looking at a
Expand All @@ -3907,17 +3906,20 @@ void StructuredBindingsCodeGenerator::InsertArg(const BindingDecl* stmt)
// Assume that we are looking at a builtin type. We have to construct the variable declaration information.
auto type = stmt->getType();

// If we have holding var we are looking at a user defined type like tuple and those the defaults from above are
// If we have a holding var we are looking at a user defined type like tuple and those the defaults from above are
// wrong. This type contains the variable declaration so we insert this.
if(const auto* holdingVar = stmt->getHoldingVar()) {
// A rvalue reference boils down to just the type. If it is a reference then it is a lvalue reference at this
// point. Hence we need to strip the &&.
// Initial paper: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0144r0.pdf

// The type of the binding depends on the initializer. In case the initializer is an lvalue we get a T&,
// otherwise a T&&. We typically look at an lvalue if the decomposition declaration was auto& [a,b]. Note the &
// here We have a rvalue in case the decomposition declaration was auto [a,b]. Note no reference. The standard
// std::get returns a lvalue reference in case e in get(e) is an lvalue, otherwise it returns an rvalue
// reference because then the call is get(std::move(e))
type = holdingVar->getType().getCanonicalType();
if(type->isRValueReferenceType()) {
type = type.getNonReferenceType();
}

bindingStmt = holdingVar->getAnyInitializer();

} else if(not type->isLValueReferenceType()) {
type = stmt->getASTContext().getLValueReferenceType(type);
}
Expand Down
4 changes: 2 additions & 2 deletions tests/Issue131.expect
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ std::tuple<int, float> foo();


std::tuple<int, float> __foo5 = foo();
int a = std::get<0UL>(static_cast<std::tuple<int, float> &&>(__foo5));
float b = std::get<1UL>(static_cast<std::tuple<int, float> &&>(__foo5));
int && a = std::get<0UL>(static_cast<std::tuple<int, float> &&>(__foo5));
float && b = std::get<1UL>(static_cast<std::tuple<int, float> &&>(__foo5));

4 changes: 2 additions & 2 deletions tests/Issue20.expect
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ int main()
{
std::map<int, int> map = std::map<int, int, std::less<int>, std::allocator<std::pair<const int, int> > >{std::initializer_list<std::pair<const int, int> >{std::pair<const int, int>{1, 2}}, std::less<int>()};
std::pair<const int, int> __operator6 = std::pair<const int, int>(map.begin().operator*());
const int key = std::get<0UL>(static_cast<std::pair<const int, int> &&>(__operator6));
int value = std::get<1UL>(static_cast<std::pair<const int, int> &&>(__operator6));
const int && key = std::get<0UL>(static_cast<std::pair<const int, int> &&>(__operator6));
int && value = std::get<1UL>(static_cast<std::pair<const int, int> &&>(__operator6));
return 0;
}

4 changes: 2 additions & 2 deletions tests/Issue381.expect
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ int main()
{
std::tuple<int, int> tup = std::tuple<int, int>{2, 5};
std::tuple<int, int> __tup6 = std::tuple<int, int>(tup);
int a = std::get<0UL>(static_cast<std::tuple<int, int> &&>(__tup6));
int b = std::get<1UL>(static_cast<std::tuple<int, int> &&>(__tup6));
int && a = std::get<0UL>(static_cast<std::tuple<int, int> &&>(__tup6));
int && b = std::get<1UL>(static_cast<std::tuple<int, int> &&>(__tup6));
return 0;
}

7 changes: 7 additions & 0 deletions tests/Issue425.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include <utility>

int main()
{
std::pair<int, char> p;
auto [a, b] = p;
}
11 changes: 11 additions & 0 deletions tests/Issue425.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include <utility>

int main()
{
std::pair<int, char> p = std::pair<int, char>();
std::pair<int, char> __p6 = std::pair<int, char>(p);
int && a = std::get<0UL>(static_cast<std::pair<int, char> &&>(__p6));
char && b = std::get<1UL>(static_cast<std::pair<int, char> &&>(__p6));
return 0;
}

12 changes: 12 additions & 0 deletions tests/StructuredBindingsHandler10Test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Source: https://en.cppreference.com/w/cpp/language/structured_binding
#include <tuple>

float x{};
char y{};
int z{};

std::tuple<float&,char&&,int> tpl(x,std::move(y),z);
const auto& [a,b,c] = tpl;
// a names a structured binding that refers to x; decltype(a) is float&
// b names a structured binding that refers to y; decltype(b) is char&&
// c names a structured binding that refers to the 3rd element of tpl; decltype(c) is const int
20 changes: 20 additions & 0 deletions tests/StructuredBindingsHandler10Test.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Source: https://en.cppreference.com/w/cpp/language/structured_binding
#include <tuple>

float x = {};

char y = {};

int z = {};


std::tuple<float &, char &&, int> tpl = std::tuple<float &, char &&, int>(x, std::move(y), z);

const std::tuple<float &, char &&, int> & __tpl9 = tpl;
float & a = std::get<0UL>(__tpl9);
char & b = std::get<1UL>(__tpl9);
const int & c = std::get<2UL>(__tpl9);

// a names a structured binding that refers to x; decltype(a) is float&
// b names a structured binding that refers to y; decltype(b) is char&&
// c names a structured binding that refers to the 3rd element of tpl; decltype(c) is const int
18 changes: 9 additions & 9 deletions tests/StructuredBindingsHandler3Test.expect
Original file line number Diff line number Diff line change
Expand Up @@ -105,25 +105,25 @@ namespace constant
{
Q q = Q();
Q __q17 = Q(q);
int a = get<0UL>(static_cast<Q &&>(__q17));
int b = get<1UL>(static_cast<Q &&>(__q17));
int c = get<2UL>(static_cast<Q &&>(__q17));
int && a = get<0UL>(static_cast<Q &&>(__q17));
int && b = get<1UL>(static_cast<Q &&>(__q17));
int && c = get<2UL>(static_cast<Q &&>(__q17));
inline constexpr bool f()
{
Q __q19 = Q(q);
int a = get<0UL>(static_cast<Q &&>(__q19));
int b = get<1UL>(static_cast<Q &&>(__q19));
int c = get<2UL>(static_cast<Q &&>(__q19));
int && a = get<0UL>(static_cast<Q &&>(__q19));
int && b = get<1UL>(static_cast<Q &&>(__q19));
int && c = get<2UL>(static_cast<Q &&>(__q19));
return ((a == 0) && (b == 1)) && (c == 4);
}
inline constexpr int g()
{
int * p = nullptr;
{
Q __q26 = Q(q);
int a = get<0UL>(static_cast<Q &&>(__q26));
int b = get<1UL>(static_cast<Q &&>(__q26));
int c = get<2UL>(static_cast<Q &&>(__q26));
int && a = get<0UL>(static_cast<Q &&>(__q26));
int && b = get<1UL>(static_cast<Q &&>(__q26));
int && c = get<2UL>(static_cast<Q &&>(__q26));
p = &c;
};
return *p;
Expand Down
4 changes: 2 additions & 2 deletions tests/StructuredBindingsHandler5Test.expect
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ int main()
Point p = Point{1, 2};
Point & __p64 = p;
double & x = __p64.get<0>();
double y = __p64.get<1>();
double && y = __p64.get<1>();
printf("x:%lf y:%lf\n", p.GetX(), p.GetY());
++x;
++y;
Expand All @@ -161,7 +161,7 @@ int main()
constexpr const Point p2 = Point{3, 4};
const Point & __p273 = p2;
const double & x2 = __p273.get<0>();
const double y2 = static_cast<const double>(__p273.get<1>());
const double && y2 = static_cast<const double>(__p273.get<1>());
return 0;
}

10 changes: 5 additions & 5 deletions tests/StructuredBindingsHandler6Test.expect
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ int main()
{
Point p = Point{1, 2};
Point __p58 = Point(p);
double x = static_cast<const Point &&>(__p58).get<0>();
double y = static_cast<const Point &&>(__p58).get<1>();
double && x = static_cast<const Point &&>(__p58).get<0>();
double && y = static_cast<const Point &&>(__p58).get<1>();
printf("x:%lf y:%lf\n", p.GetX(), p.GetY());
printf("x:%lf y:%lf\n", x, y);
char ar[2] = {7, 8};
Expand All @@ -143,9 +143,9 @@ int main()
char & c = std::get<1UL>(__muple80);
double & d = std::get<2UL>(__muple80);
std::tuple<int, char, double> __muple82 = std::tuple<int, char, double>(muple);
int ii = std::get<0UL>(static_cast<std::tuple<int, char, double> &&>(__muple82));
char cc = std::get<1UL>(static_cast<std::tuple<int, char, double> &&>(__muple82));
double dd = std::get<2UL>(static_cast<std::tuple<int, char, double> &&>(__muple82));
int && ii = std::get<0UL>(static_cast<std::tuple<int, char, double> &&>(__muple82));
char && cc = std::get<1UL>(static_cast<std::tuple<int, char, double> &&>(__muple82));
double && dd = std::get<2UL>(static_cast<std::tuple<int, char, double> &&>(__muple82));
return 0;
}

10 changes: 10 additions & 0 deletions tests/StructuredBindingsHandler8Test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <tuple>

auto tup = std::tuple<int, int>{2, 5};
auto [a1, b1] = tup;


int i = 5;
auto tup2 = std::tuple<int, int&>{2, i};
auto [a2, b2] = tup2;

19 changes: 19 additions & 0 deletions tests/StructuredBindingsHandler8Test.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include <tuple>

std::tuple<int, int> tup = std::tuple<int, int>{2, 5};

std::tuple<int, int> __tup4 = std::tuple<int, int>(tup);
int && a1 = std::get<0UL>(static_cast<std::tuple<int, int> &&>(__tup4));
int && b1 = std::get<1UL>(static_cast<std::tuple<int, int> &&>(__tup4));



int i = 5;

std::tuple<int, int &> tup2 = std::tuple<int, int &>{2, i};

std::tuple<int, int &> __tup29 = std::tuple<int, int &>(tup2);
int && a2 = std::get<0UL>(static_cast<std::tuple<int, int &> &&>(__tup29));
int & b2 = std::get<1UL>(static_cast<std::tuple<int, int &> &&>(__tup29));


4 changes: 2 additions & 2 deletions tests/StructuredBindingsHandler9Test.expect
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ std::tuple<int, float> foo = std::tuple<int, float>();


std::tuple<int, float> __foo5 = std::tuple<int, float>(foo);
int a = std::get<0UL>(static_cast<std::tuple<int, float> &&>(__foo5));
float b = std::get<1UL>(static_cast<std::tuple<int, float> &&>(__foo5));
int && a = std::get<0UL>(static_cast<std::tuple<int, float> &&>(__foo5));
float && b = std::get<1UL>(static_cast<std::tuple<int, float> &&>(__foo5));

std::tuple<int, float> & __foo6 = foo;
int & ra = std::get<0UL>(__foo6);
Expand Down
4 changes: 2 additions & 2 deletions tests/TupeInRangeBasedForTest.expect
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ int main()
std::__wrap_iter<std::tuple<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, int> *> __end1 = __range1.end();
for(; std::operator!=(__begin1, __end1); __begin1.operator++()) {
std::tuple<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, int> __operator15 = std::tuple<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>(__begin1.operator*());
std::basic_string<char, std::char_traits<char>, std::allocator<char> > s = std::get<0UL>(static_cast<std::tuple<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, int> &&>(__operator15));
int n = std::get<1UL>(static_cast<std::tuple<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, int> &&>(__operator15));
std::basic_string<char, std::char_traits<char>, std::allocator<char> > && s = std::get<0UL>(static_cast<std::tuple<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, int> &&>(__operator15));
int && n = std::get<1UL>(static_cast<std::tuple<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, int> &&>(__operator15));
printf("c=%s, n=%d\n", s.c_str(), n);
}

Expand Down
86 changes: 86 additions & 0 deletions tests/dcl.struct.bind.Test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@

// p1
// cv := denote the cv-qualifiers in the decl-specifier-seq
// S := consist of the storage-class-specifiers of the decl-specifier-seq (if any)
//
// 1) a variable with a unique name e is introduced.
// If the assignment-expression in the initializer has array type A and no ref-qualifier is present, e is defined by:
//
// attribute-specifier-seq_{opt} S cv A e ;
//
// otherwise e is defined as-if by:
//
// attribute-specifier-seq_{opt} decl-specifier-seq ref-qualifier_{opt} e initializer ;
//
// The type of the id-expression e is called E.
//
// Note: E is never a reference type
//
//
// -- tuple --
// Let i be an index of type std::size_t corresponding to vi
// either:
// e.get<i>()
// get<i>(e)
//
// In either case,
// - e is an lvalue if the type of the entity e is an lvalue reference and
// - an xvalue otherwise.
//
// -> auto& [a ,b ] -> e := lvalue
// -> auto [a ,b ] -> e := xvalue
//
// T_i := std::tuple_element<i, E>::type
// U_i := either
// - T_i&: if the initializer is an lvalue,
// - T_i&&: an rvalue reference otherwise,
//
// variables are introduced with unique names r_i as follows:
//
// S U_i r_i = initializer ;
//
// Each vi is the name of an lvalue of type Ti that refers to the object bound to ri; the referenced type is Ti.
//


// The lvalue is a bit-field if that member is a bit-field. [Example:
// struct S { int x1 : 2; volatile double y1; };
// S f();
// const auto [ x, y ] = f();
// The type of the id-expression x is “const int”, the type of the id-expression y is “const volatile double”. —end example]


// For each identifier, a variable whose type is "reference to std::tuple_element<i, E>::type" is introduced: lvalue reference if its corresponding initializer is an lvalue, rvalue reference otherwise. The initializer for the i-th variable is
// - e.get<i>(), if lookup for the identifier get in the scope of E by class member access lookup finds at least one declaration that is a function template whose first template parameter is a non-type parameter
// - Otherwise, get<i>(e), where get is looked up by argument-dependent lookup only, ignoring non-ADL lookup.
//
// The initializer for the new variable is e.get<i> or get<i>(e).
// Here the overload of get that is called is a rvalue in case we use auto and an lvalue in case we use auto&



#include <cassert>
#include <tuple>

// https://en.cppreference.com/w/cpp/language/structured_binding
float x{};
char y{};
int z{};

std::tuple<float&,char&&,int> tpl(x,std::move(y),z);
//auto tpl = std::tuple{x,std::move(y),z};
auto& [a,b,c] = tpl;
// a names a structured binding that refers to x; decltype(a) is float&
// b names a structured binding that refers to y; decltype(b) is char&&
// c names a structured binding that refers to the 3rd element of tpl; decltype(c) is int

int main() {
a = 4.5;
c = 5;

// assert(4.5 == x);
// assert(0 == z);

// std::cout << a << '\n';
// std::cout << z << '\n';
}
Loading

0 comments on commit 3fd1804

Please sign in to comment.