Search code examples
c++c++20stdvectorc++-concepts

Can I constrain a C++ function to accept an arbitrarily nested vector of a specific type?


I would like to be able to constrain a C++ function such that the only arguments it can accept are:

std::vector<T>
std::vector<std::vector<T>>
std::vector<std::vector<std::vector<T>>>
...

and so on, accepting an arbitrarily nested std::vector, but with a specific type T

I would like the following to compile:

template<typename T>
requires IsVectorInt<T>
auto foo(T t)
{

}

int main()
{
    std::vector<int> intvec {1, 2, 3};
    std::vector<std::vector<int>> vecofvec {{1, 2}, {1, 3}};
    foo(intvec);
    foo(vecofvec);
}

So far I have managed to constrain a function using concepts to only accept either:

std::vector<T> where T could itself be another std::vector<U>

or:

std::vector<int>, this however, does not accept any nested vectors.

My code is shown below:

template<class, template<class...> class>
inline constexpr bool is_specialization = false;

template<template<class...> class T, class... Args>
inline constexpr bool is_specialization<T<Args...>, T> = true;

template<class T>
concept IsVector = is_specialization<T, std::vector>;

template<class T>
concept IsVectorInt = IsVector<T> && std::is_same_v<int, typename T::value_type>;

template<typename T>
requires IsVector<T>
auto foo(T t)
{

}

int main()
{
    std::vector<int> intvec {1, 2, 3};
    std::vector<std::string> vecofstr {"bar", "baz"};
    std::vector<std::vector<int>> vecofvec {{1, 2}, {1, 3}};
    foo(intvec);
    foo(vecofvec);
    foo(vecofstr); // I would like this line to fail to compile

}

Solution

  • You can simplify and make your example more readable by creating a trait to find the inner most type of the vector and then using a static_assert as shown below:

    //trait to get the innermost type 
    template <typename T>
    struct inner_type
    {
        using type = T;
    };
    //specialization
    template <typename T>
    struct inner_type<std::vector<T>>
    {
        using type = typename inner_type<T>::type;
    };
    //alias for convenience 
    template<typename T>
    using inner_type_t = typename inner_type<T>::type;
    
    template<typename T>
    auto foo(std::vector<T> t)
    { 
        static_assert(std::is_arithmetic_v<inner_type_t<T>>);
    }  
    int main()
    {
        std::vector<int> intvec {1, 2, 3};
        std::vector<std::vector<int>> vecofvec {{1, 2}, {1, 3}};
        foo(intvec);       //works 
        foo(vecofvec);     //works 
    
        std::vector<std::string> vecofstr{"bar", "baz"};
        //foo(vecofstr);  //fails as expected
    }
    
    

    Working demo


    It is trivial to to use require(or enable_if_t) instead of static_assert as shown below:

    template<typename T>
    auto foo(std::vector<T> t) requires(std::is_arithmetic_v<inner_type_t<T>>)
    { 
    }
    

    Demo