Search code examples
c++genericsmemory-managementbindingvalue-type

C++ : rebind variable of generic type


Re-use heap memory for generic types by rebinding variables.

What I want to do:

template <typename T> //unconstrained type
//...
{
  //in some complex procedural logic
  T rebindable = .../*r-value*/;

  //...
  //some procedure dependent case:
  T rebindable = .../*different r-value*/; //rebind (I am aware this does not work)
}

Why this is a question worth asking:

Many procedural algorithms maintain and continually operate on memory throught their scope. Sometimes it is logically nessacary to completely replace one of these values, programmatically that means deconstruction and construction, potentially an indeterminate number of times (rebinding).

Typically this would be overcome by leveraging itteration, for example an implimentation of a smart-bisection root finder over potentially unassignable numeric types.

However some more complicated procedural algorithms may not be easilly written functionally, especially if rebinding is determined by procedural state.

Hence programmatically implimenting rebinding could be increadibly useful for generic algorithms.

Unsatisfactory answers

(This is by no means an attack on anyone or their answers)

Unique_ptr:

template <typename T> //still unconstrained (which is a plus)
//...
{
  auto ptr = std::unique_ptr<T>{.../*dangling reference (T*)*/};

  //...
  ptr = std::unique_ptr<T>{.../*different dangling reference*/};
}

This solves the problem of not constraining the type, however the memory being used isnt heap memory.

Assignment:

template <typename T>
  requires std::assignable_from<T, T> //type constraint!
//...
{
  T assignable = .../*r-value*/;

  //...
  assignable = .../*different r-value*/;
}

Whilst this is on heap it comes at the cost of constraining the type.

Placement new:

template <std::copy_constructible T> //type constraint!
//...
{
  T assignable = .../*r-value*/;

  //...
  assignable.~T();
  new (&assignable) T {.../*different r-value*/};
}

By deconstructing and constructing again with assignment new the value is rebound, however this still has an associated type constraint.


Solution

  • Rebind using placement new

    The last example using placement new does not require the associated type constraint!

    The expression T t = r-value; does not call the constructor, and in the same way, the expression new (&t) T {r-value}; does not call the constructor either.

    placement new bypasses the constructor with r-values

    here is an example of this behaviour: (compiles on GCC)

    #include<iostream>
    #include<utility>
    
    struct C
    {
        int n;
        static C make_new(int n) {return C{n};}
        ~C() = default;
        C(int n): n(n) {}
        C(const C &) = delete;
        C(C&&) = delete;
    };
    
    template <typename T>
    void inline rebind(T& re_bin_ref, T&& val) {
        re_bin_ref.~T();
        new (&re_bin_ref) T {val};
    }
    
    template <typename T, typename... TArgs>
    void inline emplace_rebind(T& re_bin_ref, TArgs&&... args) {
        re_bin_ref.~T();
        new (&re_bin_ref) T {std::forward<decltype(args)>(args)...};
    }
    
    template <typename T>
    void inline procedural_rebind(T& re_bin_ref, auto f) {
        re_bin_ref.~T();
        new (&re_bin_ref) T { f() };
    }
    
    int main()
    {
        
        std::cout << "\nREBINDING:\n";
        C re_bin = C::make_new(0);
        std::cout << "init: " << re_bin.n << std::endl;
        
        //...
        
        re_bin.~C();
        new (&re_bin) C {C{1}};
        std::cout << "pr-value: " << re_bin.n << std::endl;
        
        //...
        
        //re_bin.~C();
        //new (&re_bin) C {std::move<C>(C::make_new(2))}; //requires C&&
        //std::cout << "x-value: " << re_bin.n << std::endl;
        
        //...
        
        //rebind(re_bin, C::make_new(3)); //requires C&&
        //std::cout << "explicit rebind: " << re_bin.n << std::endl;
    
        //...
        
        emplace_rebind(re_bin, 4);
        std::cout << "explicit emplace rebind: " << re_bin.n << std::endl;
    
        //...
    
        procedural_rebind(re_bin, [](){return C{5};});
        std::cout << "explicit pr-value procedural rebind: " << re_bin.n << std::endl;
        
        return 0;
    }
    

    Full file on GodBolt: https://godbolt.org/z/cz7Y6qzbe