Let's consider the following proxy
struct that merely keeps track of a reference:
template<typename T>
struct proxy
{
template<typename U> proxy (const U& u) : t_(u) {}
auto operator*() const { return t_; }
const T& t_;
};
Now, we can use proxy
in the bar
function (here on godbolt):
#include <vector>
#include <span>
#include <numeric>
#include <iostream>
template<typename T>
struct proxy
{
template<typename U> proxy (const U& u) : t_(u) {}
auto operator*() const { return t_; }
const T& t_;
};
auto bar (proxy<std::span<const int>> p)
{
// The computation doesn't matter here -> just an example of use of the span contents.
return std::accumulate ((*p).begin(), (*p).end(), 0);
}
int main()
{
std::vector<int> v {1,2,3};
// EXAMPLE 1:
// The following works well (no error from valgrind).
std::cout << bar (std::span<const int> {v}) << std::endl;
// EXAMPLE 2:
// clang++ -> error: reference member 't_' binds to a temporary object whose lifetime
// would be shorter than the lifetime of the constructed object
std::cout << bar (v) << std::endl;
}
In example 1, a call to bar
with a std::span
created on the fly from an existing std::vector
works perfectly.
On the other hand, when I try in example 2 to directly pass the std::vector
to the function:
error: reference member 't_' binds to a temporary object whose lifetime would be shorter than the lifetime of the constructed object
Question 1: why does clang++ accept example 1 and not example 2 ?
Question 2: what is the flaw in the proxy
struct that makes example 2 fail ?
Question 3: it is quite annoying/worrying that g++ (12.3.1) compiles but generates a binary with UB while clang++ (12) seems smarter here by generating an error. Should one consider this is a bug in g++ ?
GCC does not implement CWG1696. This prohibits const reference members binding to temporaries in a mem-initializer.
This is prohibited because the lifetime of the temporary would end when the constructor finishes.
Consider what is called when you pass a std::vector<int>
:
template
struct proxy<std::span<const int>> {
template
proxy<std::vector<int>>(const std::vector<int>& u) : t_(u) {}
auto operator*() const { return t_; }
const std::span<const int>& t_;
};
When t_
is bound to u
, it would construct a temporary std::span<const int>
and bind it to the const reference (which is what const references do). This temporary's lifetime would end after returning from the constructor, so now it would be a dangling reference.
That means that operator*
would copy from a dangling reference, so it would be UB.
If you instead make a constructor taking a const T&
:
template<typename T>
struct proxy
{
proxy (const T& u) : t_(u) {}
auto operator*() const { return t_; }
const T& t_;
};
Then the temporary std::span<const int>
would be created in main
and be destroyed at the end of the full expression, which is after the call to bar
returns.
But at this point you might as well make bar
take a const std::span<const int>&
(and for span
specifically, you might as well take it by value)