Vladimir Shttl
Vladimir Shttl

Reputation: 61

lost value in reference c++

I have a class inherited from standard tuple:

#include <concepts>
#include <cstdint>
#include <iostream>
#include <string>
#include <tuple>
#include <vector>

template< typename ... Args >
class Identifier: public std::tuple< Args... >
{
public:
   Identifier( Args... args ) requires( sizeof...( Args ) != 0 ) : std::tuple< Args... >( std::move( args )... )
   {}

   Identifier( std::tuple< Args... > tuple ):std::tuple< Args... >( tuple )
   {}

   Identifier()
   {}

   template< typename ... T >
   requires std::convertible_to< std::tuple< Args... >, std::tuple< T... > >
   Identifier( const Identifier< T... >& identifier ): std::tuple< Args... >(static_cast< std::tuple< T... > >(identifier))
   {}
};

int main()
{
   Identifier<std::wstring, std::int64_t>  item(L"id", 10);
   Identifier<const std::wstring&, std::int64_t> item_ref = item;

   std::vector< Identifier< const std::wstring&, std::int64_t > > collection;
   collection.emplace_back( item );
}

At this point:

Identifier<const std::wstring&, int64_t> item_ref = item;

item_ref has correct value.

Then I put item into a vector:

std::vector< Identifier< const std::wstring&, std::int64_t > > collection;
   collection.emplace_back( item );

I got invalid value in string reference in element in vector and invalid value in string reference in item_ref.

Why did that happen, and how do I fix it?

Upvotes: -1

Views: 135

Answers (1)

Joel
Joel

Reputation: 1678

You firstly use the Identifier(Args...) constructor, then you use the other one twice.

template< typename ... T >
requires std::convertible_to< std::tuple< Args... >, std::tuple< T... > >
Identifier( const Identifier< T... >& identifier ): std::tuple< Args... >(static_cast< std::tuple< T... > >(identifier))
{}

When item is passed to it, firstly a const Identifier<T...>& to it is created as argument to the constructor. Then this reference is static_casted to a std::tuple<T...>.

Note that T... is std::wstring, int64_t, as this is items bases templated types.

This means that you cast away the reference, resulting in a temporary copy of the original item. Then you assign that copy's std::wstring member to a const std::wstring& via the construction of the base. After the construction, the temporary gets destroyed, leaving the object referencing an already deleted std::wstring.

To solve your problem you could e.g. just drop the static_cast and pass the argument as is.


As @TedLyngmo mentioned here, you most likely have the order of the std::convertible_to reversed.

It should be

// std::convertible_to<From, To>;
std::convertible_to<std::tuple<T... >, std::tuple<Args...>>

Which could be further modified to the following:

Identifier(std::convertible_to<std::tuple<Args...>> auto&& identifier)
        : std::tuple<Args...>(std::forward<decltype(identifier)>(identifier)) {}

This uses std::forward on the forwarding reference to perfectly forward any arguments you pass to it. You could apply this to the other constructors as well.


I got invalid value in string reference in element in vector and invalid value in string reference in item_ref.

How do you know? Did you print the values to std::wcout? Well, at least I tried to do this and (depending on how I compiled it) I got either garbage output or even a crash.

I suggest you enable all warnings (I still got none) and enable other tools like -fsanitize=undefined which resulted in the following:

UndefinedBehaviorSanitizer:DEADLYSIGNAL
==1==ERROR: UndefinedBehaviorSanitizer: stack-overflow on address 0x7ffd94afc000 (pc 0x7ee02548a31e bp 0x59c5bf4e1e6b sp 0x7ffd94afb190 T1)
    #0 0x7ee02548a31e in __gnu_cxx::stdio_sync_filebuf<wchar_t, std::char_traits<wchar_t>>::xsputn(wchar_t const*, long) (/opt/compiler-explorer/gcc-14.2.0/lib64/libstdc++.so.6+0x12331e) (BuildId: 998334304023149e8c44e633d4a2c69800a2eb79)
    #1 0x7ee0254b65e4 in std::basic_ostream<wchar_t, std::char_traits<wchar_t>>& std::__ostream_insert<wchar_t, std::char_traits<wchar_t>>(std::basic_ostream<wchar_t, std::char_traits<wchar_t>>&, wchar_t const*, long) (/opt/compiler-explorer/gcc-14.2.0/lib64/libstdc++.so.6+0x14f5e4) (BuildId: 998334304023149e8c44e633d4a2c69800a2eb79)
    #2 0x59c5bf4e11c7 in main /app/example.cpp:35:15
    #3 0x7ee025029d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)
    #4 0x7ee025029e3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)
    #5 0x59c5bf4b4494 in _start (/app/output.s+0xa494)

SUMMARY: UndefinedBehaviorSanitizer: stack-overflow (/opt/compiler-explorer/gcc-14.2.0/lib64/libstdc++.so.6+0x12331e) (BuildId: 998334304023149e8c44e633d4a2c69800a2eb79) in __gnu_cxx::stdio_sync_filebuf<wchar_t, std::char_traits<wchar_t>>::xsputn(wchar_t const*, long)
==1==ABORTING
id

https://godbolt.org/z/5eja84EaM

Upvotes: 3

Related Questions