-1

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?

6
  • Identifier<std::wstring, int64_t> and Identifier<const std::wstring&, int64_t> are different unrelated types. Makes me think that on item_ref = item, maybe the compiler is making a temp copy, and you are taking a reference to the temp, and then the temp is gone by the time you reach the emplace_back(). You should use your debugger to verify the actual flow, look at the memory addresses involved, etc. Commented Feb 5 at 20:26
  • 1
    Probably unrelated, but your parameters to std::convertible_to are backwards. Commented Feb 5 at 20:27
  • static_cast< std::tuple< T... > >(identifier) This creates a temporary copy of identifier. You might have meant static_cast< const std::tuple< T... >& >(identifier). Or drop the cast entirely, things should work without. Commented Feb 5 at 20:41
  • If a program declares an explicit or partial specialization of std::tuple, the program is ill-formed, no diagnostic required. - preference on the page for tuple. You are treading on thin ice. Commented Feb 5 at 21:29
  • 2
    @Rud48 There is no specialization here. This is just inheritance. Not that it's usually a good idea to inherit from classes in the standard library, but it doesn't make the program ill-formed. Commented Feb 5 at 21:47

1 Answer 1

3

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

Sign up to request clarification or add additional context in comments.

4 Comments

Got my vote but you've reversed the template parameters in std::convertible_to<From, To> in your demo (just like OP did in the Q). A simpler version: Identifier(std::convertible_to<std::tuple<Args...>> auto&& identifier : std::tuple<Args...>(std::forward<decltype(identifier)>(identifier)) {}
@TedLyngmo Yes, you are right. I just blindly copied it as it did not change anything related to the unexpected behavior OP is experiencing in their example. I'll add it to the answer.
Compiling with GCC 14.2 and C++26 there is a dangling reference from the static_cast, The root problem may be inheriting from tuple.
@Rud48 Yes, the static_cast creates a temporary std::tuple<T...> (that is, a temporary std::tuple<std::wstring, int64_t> ) from the const Identifier<T...>& so the reference to the wstring becomes dangling at the end of the full expression, which is why Joel says OP should drop the static_cast. That way the wstring& will bind to the wstring in item instead of the temporary.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.