Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

static array of reference_wrapper

46 views
Skip to first unread message

Marcel Mueller

unread,
Oct 15, 2022, 6:42:27 PM10/15/22
to
Hi

when I try to initialize a static array of references using
reference_wrapper I get a compiler error:

struct ColumnRenderer
{ // ...
};

static const std::reference_wrapper<const ColumnRenderer> Renderers[] =
{ ColumnRenderer(),
ColumnRenderer()
};

conversion from ‘ColumnRenderer’ to non-scalar type
‘std::reference_wrapper<const ColumnRenderer>’ requested


If I do the same with a simple custom reference wrapper it works:

template <typename T>
struct reference
{ T& Ref;
constexpr reference(T& ref) : Ref(ref) { }
constexpr operator T&() const { return Ref; }
};

static const reference<const ColumnRenderer> Renderers[] =
{ ColumnRenderer(),
ColumnRenderer()
};


Why? I thought reference_wrapper is intended to be used with arrays.
Or is this one of the blemishes of C++11 that has been fixed in later
versions?


Marcel

daniel...@gmail.com

unread,
Oct 15, 2022, 8:37:28 PM10/15/22
to
On Saturday, October 15, 2022 at 6:42:27 PM UTC-4, Marcel Mueller wrote:
>
> when I try to initialize a static array of references using
> reference_wrapper I get a compiler error:
>
> struct ColumnRenderer
> { // ...
> };
>
> static const std::reference_wrapper<const ColumnRenderer> Renderers[] =
> { ColumnRenderer(),
> ColumnRenderer()
> };
>
> conversion from ‘ColumnRenderer’ to non-scalar type
> ‘std::reference_wrapper<const ColumnRenderer>’ requested
>

I would understand your example better if it were written as

ColumnRenderer renderer1;
ColumnRenderer renderer2;

static const std::reference_wrapper<const ColumnRenderer> Renderers[] =
{ renderer1,
renderer2
};

(which I'm sure compiles.) But what is the point of initializing it with
ColumnRenderer() ... ColumnRenderer()? I don't believe std::reference_wrapper
has a constructor that would allow an implicit conversion from those, see
https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper/reference_wrapper

Daniel




Andrey Tarasevich

unread,
Oct 16, 2022, 12:00:55 AM10/16/22
to
On 10/15/2022 3:42 PM, Marcel Mueller wrote:
>
> Why? I thought reference_wrapper is intended to be used with arrays.
> Or is this one of the blemishes of C++11 that has been fixed in later
> versions?

`std::reference_wrapper<>` is not a reference. It will not extend the
lifetime of temporaries the same way a true reference does. How did you
expect your code to work?

To prevent you from making such erroneous assumptions and running into
subsequent problems, the class specification has a dedicated requirement
(see
https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper/reference_wrapper):

---

template< class U >
constexpr reference_wrapper( U&& x ) noexcept(/*see below*/) ;

Converts x to T& as if by T& t = std::forward<U>(x), then stores a
reference to t. This overload participates in overload resolution only
if typename std::decay<U>::type is not the same type as
reference_wrapper and the expression FUN(std::declval<U>()) is
well-formed, where FUN names the set of imaginary functions

void FUN(T&) noexcept;
void FUN(T&&) = delete;

---

That `FUN` part is the one intended to reject binding to non-lvalues.
Which is exactly what happens in your case.

> If I do the same with a simple custom reference wrapper it works:
>
> template <typename T>
> struct reference
> { T& Ref;
> constexpr reference(T& ref) : Ref(ref) { }
> constexpr operator T&() const { return Ref; }
> };

No, it doesn't. It merely "compiles". But the temporaries get destroyed
almost immediately and you end up with a bunch of dangling references.

--
Best regards,
Andrey

Andrey Tarasevich

unread,
Oct 16, 2022, 12:09:36 AM10/16/22
to
On 10/15/2022 9:00 PM, Andrey Tarasevich wrote:
> reference_wrapper and the expression FUN(std::declval<U>()) is
> well-formed, where FUN names the set of imaginary functions
>
>   void FUN(T&) noexcept;
>   void FUN(T&&) = delete;
>
> ---
>
> That `FUN` part is the one intended to reject binding to non-lvalues.
> Which is exactly what happens in your case.

Hm... Looks like my explanation is not entirely accurate. The argument
of `FUN` is supposed to be `std::declval<U>()`, which should pass the
test in this case.

It does look like the standard constructor is rejecting your
temporaries. And it is true that you would end up with dangling
references if it accepted them. But the explanation of the underlying
mechanism that I provided is probably inaccurate.

--
Best regards,
Andrey

Andrey Tarasevich

unread,
Oct 16, 2022, 12:30:45 AM10/16/22
to
No, everything is fine with my explanation. This trick is based on how
universal references, reference-collapsing rules and `std::declval<>`
interact with each other.

When you pass a non-lvalue to the constructor of
`std::reference_wrapper`, type `U` is deduced as a plain (non-reference)
type (`ColumnRenderer ` is your case). Then
`std::declval<ColumnRenderer>` produces a `ColumnRenderer> &&` result.
This result is an xvalue, which cannot serve as an argument for `FUN(T
&)`. Compilation fails. This is why the constructor rejects non-lvalue
arguments.

When you pass an lvalue to the constructor of `std::reference_wrapper`,
type `U` is deduced as an lvalue reference type. Then `std::declval<>`
produces an lvalue reference result. That result can serve as an
argument for `FUN(T &)`, which is why the constructor accepts lvalues.

--
Best regards,
Andrey



Chris M. Thomasson

unread,
Oct 16, 2022, 12:37:17 AM10/16/22
to
Fwiw:
______________________________
#include <iostream>
#include <functional>


struct render
{
int m_foo;
};


static render g_farm_concrete[] = {
{ 0 },
{ 1 },
{ 2 }
};


static const std::reference_wrapper<const render> g_farm_refs[] = {
g_farm_concrete[0],
g_farm_concrete[1],
g_farm_concrete[2]
};


int main()
{
std::cout << "g_farm_concrete[0]" << &g_farm_concrete[0] << "\n";
std::cout << "g_farm_concrete[1]" << &g_farm_concrete[1] << "\n";
std::cout << "g_farm_concrete[2]" << &g_farm_concrete[2] << "\n";

std::cout << "\n";

std::cout << "g_farm_refs[0]" << &g_farm_concrete[0].m_foo << "\n";
std::cout << "g_farm_refs[1]" << &g_farm_concrete[1].m_foo << "\n";
std::cout << "g_farm_refs[2]" << &g_farm_concrete[2].m_foo << "\n";

return 0;
}
______________________________


Output:

g_farm_concrete[0]0x5616e1d98010
g_farm_concrete[1]0x5616e1d98014
g_farm_concrete[2]0x5616e1d98018

g_farm_refs[0]0x5616e1d98010
g_farm_refs[0]0x5616e1d98014
g_farm_refs[0]0x5616e1d98018



Chris M. Thomasson

unread,
Oct 16, 2022, 12:41:45 AM10/16/22
to
God damn it! I accidentally posted the old output when I realized I
messed up the index for g_farm_refs[0], I forgot the [1] and[2]:

corrected:

g_farm_concrete[0]0x563ec9835010
g_farm_concrete[1]0x563ec9835014
g_farm_concrete[2]0x563ec9835018

g_farm_refs[0]0x563ec9835010
g_farm_refs[1]0x563ec9835014
g_farm_refs[2]0x563ec9835018

Sorry about that nonsense. Fwiw, the pointers are going to the same
locations, but this still seems a little bit, sort of, "odd" to me wrt
an array of constant references to the g_farm_concrete array.

Chris M. Thomasson

unread,
Oct 16, 2022, 12:45:27 AM10/16/22
to
On 10/15/2022 9:41 PM, Chris M. Thomasson wrote:
> On 10/15/2022 9:36 PM, Chris M. Thomasson wrote:
>> On 10/15/2022 5:37 PM, daniel...@gmail.com wrote:
>>> On Saturday, October 15, 2022 at 6:42:27 PM UTC-4, Marcel Mueller wrote:
[...
> Sorry about that nonsense. Fwiw, the pointers are going to the same
> locations, but this still seems a little bit, sort of, "odd" to me wrt
> an array of constant references to the g_farm_concrete array.

Damn bugs I forgot to actually use the g_farm_refs. I should create a
new post:
_________________________________
#include <iostream>
#include <functional>


struct render
{
int m_foo;
};


static render g_farm_concrete[] = {
{ 0 },
{ 1 },
{ 2 }
};


static const std::reference_wrapper<const render> g_farm_refs[] = {
g_farm_concrete[0],
g_farm_concrete[1],
g_farm_concrete[2]
};


int main()
{
std::cout << "g_farm_concrete[0]" << &g_farm_concrete[0] << "\n";
std::cout << "g_farm_concrete[1]" << &g_farm_concrete[1] << "\n";
std::cout << "g_farm_concrete[2]" << &g_farm_concrete[2] << "\n";

std::cout << "\n";

std::cout << "g_farm_refs[0]" << &g_farm_refs[0].get().m_foo << "\n";
std::cout << "g_farm_refs[1]" << &g_farm_refs[1].get().m_foo << "\n";
std::cout << "g_farm_refs[2]" << &g_farm_refs[2].get().m_foo << "\n";

return 0;
}
_________________________________


Marcel Mueller

unread,
Oct 16, 2022, 4:01:02 AM10/16/22
to
Am 16.10.22 um 06:00 schrieb Andrey Tarasevich:
>> If I do the same with a simple custom reference wrapper it works:
>>
>> template <typename T>
>> struct reference
>> { T& Ref;
>>   constexpr reference(T& ref) : Ref(ref) { }
>>   constexpr operator T&() const { return Ref; }
>> };
>
> No, it doesn't. It merely "compiles". But the temporaries get destroyed
> almost immediately and you end up with a bunch of dangling references.

You are right. The constructor call will use a temporary.
The helper need to be changed to:

template <typename T>
struct reference
{ T& Ref;
constexpr operator T&() const { return Ref; }
};

In this case it is a struct initialization rather than a constructor
which will extend the lifetime.

I have seen this in an old project and tried to migrate it to
std::reference_wrapper which is not possible.


Marcel

0 new messages