c++templatestype-traits

How to Deduce Template Parameter T from Another Type U in C++?


I simplified my class MyVector in C++ by combining the lvalue and rvalue constructors into a single universal reference constructor. However, this change has led to a problem: the template parameter T of my class cannot be deduced from the universal reference parameter U in the constructor.

Here’s the definition of my class:

template<typename T>
class MyVector {
public:
    std::vector<T> data;
    ...
    
    template<typename U>
    MyVector(U&& data) : data(std::forward<U>(data)) {}
};

The consequence of this limitation is that using my class becomes inconvenient. Instead of being able to instantiate it like this:

MyVector v = std::vector<int>({1, 2, 3});

I am forced to specify the template parameter explicitly:

MyVector<int> v = std::vector<int>({1, 2, 3});

Problem:

I would like to enforce that std::decay_t<U> must be a std::vector<T> and, in turn, deduce T from U. It’s important to note that I do not want to simply ignore the definition of this constructor if std::decay_t<U> != std::vector<T>, which I could achieve using std::enable_if. Instead, I want to bind std::decay_t<U> to std::vector<T> in a way that enforces equality between these types, allowing type deduction to take place. Is there a way to achieve this in C++ using type traits or other techniques?

What I have tried:

I tried to solve this problem by creating a method bind in the class MyVector that takes one parameter of type std::vector<T> and passing std::declval<std::decay_t<U>>() to this function. Unfortunately, this failed with an error indicating that the result of std::declval must be unused. I have no other ideas on how to bind U to std::vector<T> in a way that would allow for type deduction. Is there a way to achieve this in C++ using type traits or other techniques?


Solution

  • You can add a deduction guide.

    template <typename U>
    MyVector(U) -> MyVector<typename U::value_type>;
    

    You may optionally want to further restrict the constructor to only accept std::vector.

    template<typename U> 
      requires std::is_same_v<std::decay_t<U>, 
                              std::vector<typename std::decay_t<U>::value_type>>
    MyVector(U&& data) : data(std::forward<U>(data)) {}