Search code examples
c++c++11rvo

RVO with a standard layout struct without any constructors


I have a struct representing a binary message. I want to write a function to get the next such record from a buffer (whether a file or a socket, doesn't matter):

template <typename Record>
Record getNext();

Now, I could write this like:

template <typename Record>
Record getNext() {
    Record r;
    populateNext(reinterpret_cast<char*>(&r),  // maybe ::read()
                 sizeof(r));                   // or equivalent
    return r;
}

which is nice and gives me the benefits of RVO. However, it will invoke the default constructor of Record, which may be composed of types with non-trival default constructors which do work that I would like to avoid - these are not necessarily POD types, but they are standard layout.

Is there a way to write getNext() such that we avoid any constructors (default or copy/move) on Record? Ideally, when the user calls:

auto record = getNext<Record>();

The buffer is read directly into the memory of record. Is this possible?


Solution

  • no_init is a constant of type no_init_t.

    If you construct a pod from a no_init_t, you get an uninitialized pod, and (assuming elision) there is nothing to be done.

    If you construct a non-pod from a no_init_t, you have to override a constructor, and make it not initialize the data. Usually class_name(no_init_t):field1(no_init), field2(no_init){} will do it, and sometimes class_name(no_init_t){} will do it (assuming all contents are pod).

    Constructing from no_init on each member can act as a sanity check that the members are indeed pod, however. A non-pod class constructed from no_init will fail to compile until you write the no_init_t constructor.

    This (having to no_init each member constructor) does generate some annoying DRY failure, but we don't got reflection, so you are gonna repeat yourself and like it.

    namespace {
      struct no_init_t {
        template<class T, class=std::enable_if_t<std::is_pod<T>{}>>
        operator T()const{
          T tmp;
          return tmp;
        }
        static no_init_t instance() { return {}; }
        no_init_t(no_init_t const&) = default;
      private:
        no_init_t() = default;
      };
      static const no_init = no_init_t::instance();
    }
    
    
    struct Foo {
      char buff[1000];
      size_t hash;
      Foo():Foo(""){}
      template<size_t N, class=std::enable_if_t< (N<=sizeof(buff)) >>
      Foo( char const(&in)[N] ) {
        // some "expensive" copy and hash
      }
      Foo(no_init_t) {} // no initialization!
    };
    struct Record {
      int x;
      Foo foo;
      Record()=default;
      Record(no_init_t):
        x(no_init), foo(no_init)
      {}
    };
    

    Now we can construct Record with no_init and it won't be initialized.

    Every POD class is not initialized. Every non-POD class must provide a no_init_t constructor (and presumably implement non-initialization, as best it can).

    You then memcpy right over it.

    This requires modifying your type, and the types it contains, to support non-initialization.