Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Forwarding reference behavior with definite types

Suppose I have a template class

template <typename T> class foo;
template <typename... Args>
struct foo<std::tuple<Args...>> {
  std::tuple<Args...> t;
  foo(Args&&... args): t{std::forward<Args>(args)...} { }
};

I understand that in this case Args&&... are rvalue references, and I could have just as well written std::move instead of std::forward.

I can also have a constructor with lvalue references, like so

foo(const Args&... args): t{args...} { }

The question is whether it's possible to get the same behavior as with forwarding references, but for definite types? The reason I want this is so I can used syntax like

foo bar({. . .}, {. . .}, . . ., {. . .});

This works if I define the foo(Args&&... args) constructor, but doesn't allow for a mixed scenario, where I want to initialize some of the member tuple elements with brace-enclosed initializer lists and have others copied from preexisting object instances.

like image 704
SU3 Avatar asked Jan 07 '17 20:01

SU3


People also ask

What is a forwarding reference?

I understand that a forwarding reference is "an rvalue reference to a cv-unqualified template parameter", such as in. template <class T> void foo(T&& ); which means the above function can take both l-value and r-value reference.

What is a forwarding universal reference?

A forwarding reference is an rvalue reference to a cv-unqualified template parameter. If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction. Hence, the two mean the same thing, and the current C++ standard term is forwarding reference.

What is perfect forwarding?

What is Perfect Forwarding. Perfect forwarding allows a template function that accepts a set of arguments to forward these arguments to another function whilst retaining the lvalue or rvalue nature of the original function arguments.

How does STD forward work?

std::forward has a single use case: to cast a templated function parameter (inside the function) to the value category (lvalue or rvalue) the caller used to pass it. This allows rvalue arguments to be passed on as rvalues, and lvalues to be passed on as lvalues, a scheme called “perfect forwarding.”


1 Answers

Sure; there is a fancy and simple way.

The fancy way I will detail below. First the simple way: take by value.

template <typename... Args>
struct foo<std::tuple<Args...>> {
  std::tuple<Args...> t;
  foo(Args... args): t{std::forward<Args>(args)...} { }
};

Really, just do this. Forward is used to do the right thing if Args contains a reference.

Taking by value adds one move over perfect forwarding, but reduces the requirement for overloads exponentially.


This is the fancy way. We type erase construction:

template<class T>
struct make_it {
  using maker=T(*)(void*);
  maker f;
  void* args;
  // make from move
  make_it( T&& t ):
    f([](void* pvoid)->T{
      return std::move(*static_cast<T*>(pvoid));
    }),
    args(std::addressof(t))
  {}
  // make from copy
  make_it( T const& t ):
    f([](void* pvoid)->T{
      return *(T const*)(pvoid);
    }),
    args(std::addressof(t))
  {}
  operator T()&&{return std::move(*this)();}
  T operator()()&&{ return f(args); }
};

This type erases construction by copy or move.

template <typename... Args>
struct foo<std::tuple<Args...>> {
  std::tuple<Args...> t;
  foo(make_it<Args>... args): t{std::move(args)()...} { }
};

It isn't perfectly transparent, but it is as close as I can get.

Double {{}} is required instead of single. It is a user defined conversion, so another one will not implicitly be done. We could add a universal ctor:'

  // make from universal
  template<class U>
  make_it( U&& u ):
    f([](void* pvoid)->T{
      return std::forward<U>(*(U*)(pvoid));
    }),
    args(std::addressof(u))
  {}

which works better if we add a sfinae teat that U&& can be used to implicitly construct T.

It has a few advantages, but they are marginal over just taking by value. For example, in C++17 non-movable types can be perfect forward constructed in some cases.

like image 115
Yakk - Adam Nevraumont Avatar answered Oct 13 '22 06:10

Yakk - Adam Nevraumont