2

I do not understand well enough why the second call of func in the code below does not work:

#include <string>
#include <iostream>
#include <tuple>

using Tuple = std::tuple<const int&, const std::string&>;

void func(const Tuple& t)
{
    std::cout << std::get<1u>(t) << std::endl;
}

int main()
{
    const int n = 3;
    const std::string text = "xyz";
    
    func(Tuple(n, text));

    //Does not work:
    //func(Tuple(5, "abc"));

    return 0;
}

When is the temporary string "abc" destroyed?

EDIT1

So, finally, according to 康桓瑋 this works:

    func(Tuple(5, std::string("abc")));

but this does not:

    Tuple t(5, std::string("abc"));
    func(t);

right? If yes what is the difference?

Alexey Starinsky
  • 3,699
  • 3
  • 21
  • 57
  • 1
    I'm not sure but I believe its because the lifetime of the temporary is extended to the lifetime of the reference it is bound to (the one in Tuple's constructor), so by the time you call func, the temporary string has been destroyed already and you're basically in UB land by that point. [this question may help](https://stackoverflow.com/questions/6936720/why-lifetime-of-temporary-doesnt-extend-till-lifetime-of-enclosing-object) – Borgleader Jul 13 '22 at 03:48

2 Answers2

11

Your second case is exactly what the standard wants to detect and forbid.

Since "abc" is not a std::string type, quote from P2255:

std::tuple<const std::string&> t("meow");

This construction always creates a dangling reference, because the std::string temporary is created inside the selected constructor of tuple (template<class... UTypes> tuple(UTypes&&...)), and not outside it. Thus, unlike string_view’s implicit conversion from rvalue strings, under no circumstances can this construction be correct.

And Tuple(5, "abc") is no longer well-formed in C++23 thanks to P2255.

To prevent the destruction of temporary strings, you can explicitly specify the std::string type such as func(Tuple(5, std::string("abc"))).

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • But that example is a *named* object that has a lifetime that extends past the statement. The OP's example is an unnamed Tuple object which has the same lifetime as the unnamed std::string object, so there will be no dangling. – Chris Dodd Jul 13 '22 at 04:18
  • 1
    @ChrisDodd This tuple is dangling as soon as it is constructed. Because we leave the constructor. – 康桓瑋 Jul 13 '22 at 04:24
  • `func(Tuple(5, std::string("abc")))` works with GCC. – Alexey Starinsky Jul 13 '22 at 04:41
  • I did not understand the difference between `std::string("abc")` and `"abc"`. My understanding was that `"abc"` converted `std::string("abc")`, right? – Alexey Starinsky Jul 13 '22 at 04:53
  • 1
    @Alexey Starinsky For `tuple(string("abc"))`, `const string&` is bound to the temporary `string("abc")` you pass in; But for `"abc"`, `const string&` is bound to a temporary `string` variable created *inside* the `tuple` constructor, which is destroyed at the end of the constructor. – 康桓瑋 Jul 13 '22 at 05:44
  • @康桓瑋 For `tuple(string("abc"))`, when is `string("abc")` destroyed? – Alexey Starinsky Jul 13 '22 at 20:03
  • @AlexeyStarinskyA When the expression `tuple(string("abc"))` ends. – 康桓瑋 Jul 14 '22 at 01:31
3

As hinted at in the comments, the problem is not with the parameter to the call to func, per se, but with the second parameter to the Tuple constructor.

You are attempting to assign a reference to a string constructed as a temporary object (from the given const char* literal) to the second member of the tuple; when the constructor has completed/returned, that reference will invalid.

Some compilers may accept the code (MSVC does) and it may seem to work. However, clang-cl (in Visual Studio 2022) gives the following error when uncommenting the 'offending' line in your code:

error : reference member '_Val' binds to a temporary object whose lifetime would be shorter than the lifetime of the constructed object

However, note the the exact same error is shown if we replace your one-line call to func with the following two lines:

    Tuple tup = Tuple(5, "abc"); // Error on this line
    func(tup);
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • Seems like clang's error message is patently wrong -- the lifetime of the temporary object bound is *the same* as the lifetime of the Tuple it is bound in. If you explicitly bind the Tuple to a name (which the OP does not do), only then is the lifetime of the Tuple extended and dangling problem occurs. – Chris Dodd Jul 13 '22 at 04:21
  • @ChrisDodd Not 100% sure, but I think the lifetime of the temporary string ends when the tuple's constructor ends, irrespective of whether that constructs a named/unnamed object. – Adrian Mole Jul 13 '22 at 04:31