Lvalue reference vs object data member.

I have a bit of a problem in my code. Here is a simplified class that portrays the kind of problem I am facing:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<typename T = myCustomDataType,
         template<typename...> class Container = std::vector>
class ExampleClass {
  public:
    template<typename U = Container<T>,
             typename = decltype(((Container<T>*)nullptr)
                                           ->resize(NULL))>
     ExampleClass(U&& arr)
        : myContainer{std::forward<U>(arr)}
     {
       //...
     }

//...
  private:
    Container<T> myContainer; //Reference or regular object?

};

In this code, arr can be either an lvalue reference or an rvalue reference. In the rvalue case, it is moved, while in the lvalue case, it is copied by value.

However, I want the lvalue case to make myContainer hold a lvalue reference to the original passed container (usually std::vector). This is both for efficiency and also so the original passed container will be modified. But if I do this:

 
Container<T>& myContainer;


Then the rvalue case no longer works because it is a reference. I need myContainer to be able to be changed by the class, so making it a const& and binding rvalues to it that way is out of the question. What is the best option to proceed? Is it possible to determine the type of myContainer as either a reference or an actual object based on what is passed to the forwarding constructor?
Last edited on
Is it possible to determine the type of myContainer as either a reference or an actual object based on what is passed to the forwarding constructor?
You'd need another level of indirection (a make_example_class() function template, or CTAD with a user-defined deduction guide) to perform the type computation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename T, typename = decltype(std::declval<T>().resize(NULL))>
struct example_class
{
  example_class(T container)
    : container_{std::forward<T>(container)}
  {}

  T container_; 
};

template <typename T>
example_class(T&& x) -> 
  example_class<
    std::conditional_t<std::is_lvalue_reference<T>::value, T&,
      std::remove_reference_t<T>>>;


Also, be careful with the constraints on line 5-7 in the original post. The type of the expression expanded from the macro NULL varies between implementations. In my opinion, it is also less subtle to call std::declval with an lvalue reference template argument - the goal is to make it more explicit that the value category of the implicit object argument is intentionally lvalue:
decltype(std::declval<U&>().resize(NULL))

Live demo:
http://coliru.stacked-crooked.com/a/c4d95a914c76638d

P.S., this doesn't need to be in the beginner's section.
Last edited on
How can I make that work if my template parameter is a template template, rather than a regular template parameter T? I want it to work with Container<T>

Right now I am getting a type/value mismatch error. It says "note: expected a class template, got std::conditional<..."
Last edited on
Like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename C, template <typename...> class TT, typename... TArgs>
struct example_class
{ 
  template <typename = decltype(std::declval<C>().resize(NULL))>
  example_class(C&& container)
    : container_{std::forward<C>(container)}
  {}
  
  C container_;
};

template <template <typename...> class TT, typename... TArgs>
example_class(TT<TArgs...>&&) -> example_class<TT<TArgs...>, TT, TArgs...>;
template <template <typename...> class TT, typename... TArgs>
example_class(TT<TArgs...>&) -> example_class<TT<TArgs...>&, TT, TArgs...>;


Although I would contest that template template arguments are a major source of difficulty and should really be avoided if at all possible.
Last edited on
Topic archived. No new replies allowed.