Search code examples
c++c++11tuplesvariadicreference-counting

std::tuple<> of one std::shared_ptr<> does not work?


I recently discovered a problem with using std::tuple<> for just one element. I created a class for type erasure and retaining N number of reference counted objects. However the reference counted object is not retained if it is the only one in the std::tuple<>.

Am I doing something wrong?

class token {
public:
  template<typename... Types>
  token(Types... types) : _self(std::make_shared<const std::tuple<Types...>>(std::make_tuple(std::move(types)...))) {}

  // Why do I need this special version of the constructor?
  // Uncomment and the code will work!
  //template<typename T>
  //token(T t) : _self(std::make_shared<const T>(std::move(t))) {}
private:
  std::shared_ptr<const void> _self;
};

Example (tested with Xcode 8.0):

token make_token() {
  std::shared_ptr<int> shared(new int(), [](int* i) {
    // Called immediately if using only tuple constructor!
  });
  return token(shared);
}
token my_token = make_token(); // std::shared_ptr<> is already gone!

Solution

  • From my perspective, your code should work fine, msvc and gcc seems to agree with me in this snippet. From T.C. comment, this is looked like the real issue with clang and is fixed in clang trunk

    As a workaround for now, I suggest this approach, (special_decay_t is taken from cppreference):

    #include <iostream>
    #include <tuple>
    #include <memory>
    
    template <class T>
    struct unwrap_refwrapper
    {
        using type = T;
    };
    
    template <class T>
    struct unwrap_refwrapper<std::reference_wrapper<T>>
    {
        using type = T&;
    };
    
    template <class T>
    using special_decay_t = typename unwrap_refwrapper<typename std::decay<T>::type>::type;
    
    class token {
    public:
      template<typename... Types>
      token(Types&&... types) : _self(std::make_shared<std::tuple<special_decay_t<Types>...>>(std::forward<Types>(types)...)) {}
    
    private:
      std::shared_ptr<void> _self;
    };
    
    
    token make_token() {
      return token(std::shared_ptr<int>(new int(), [](int* i) {
        std::cout << "freed\n";
        delete i;
      }));
    }
    
    int main()
    {
        token my_token = make_token();
        std::cout << __LINE__ << '\n';
    }
    

    See this demo