Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Print simply STL vectors of vectors recursively in C++

I have used the following code which work well for printing a simple std::vector with printContainer().
I want now to extend it for nested containers with printContainerV2()
I have tried using template to determine if the type is a stl container but it does not seem to be the way to do it.

#include <iostream>
#include <iterator>
#include <vector>

template <typename Iter, typename Cont>
bool isLast(Iter iter, const Cont& cont)
{
    return (iter != cont.end()) && (next(iter) == cont.end());
}


template <typename T>
struct is_cont {
    static const bool value = false;
};

template <typename T,typename Alloc>
struct is_cont<std::vector<T,Alloc> > {
    static const bool value = true;
};


template <typename T>
std::string printContainer(T const& container)
{
    std::string str = "{";
    for (auto it = std::begin(container); it != std::end(container); ++ it)
        if (isLast(it, container))
                str = str + std::to_string(*it) + "}";
        else
                str = str + std::to_string(*it) + ",";
    return str;
}
/*
template <typename T>
std::string printContainerV2(T const& container)
{
    std::string str = "{";
    for (auto it = std::begin(container); it != std::end(container); ++ it)
        if (isLast(it, container))
            if (is_cont<decltype(*it)>::value == true)
                str = str + printContainer(*it);
            else
                str = str + std::to_string(*it) + "}";
        else
            if (is_cont<decltype(*it))>::value == true)
                str = str + printContainer(*it);
            else
                str = str + std::to_string(*it) + ",";
    return str;
}
*/
int main()
{
    std::vector<int> A({2,3,6,8});
    std::vector<std::vector<int>> M(2,A);
    M[1][0] ++;

    std::cout << is_cont<decltype(A)>::value << std::endl;  // returns true !

    for (auto it = std::begin(M); it != std::end(M); ++ it)
    {
        std::cout << printContainer(*it) << std::endl; // works well std::vector<int>
        std::cout << is_cont<decltype(*it)>::value << std::endl; // return false :(
    }

    // Want to use this for printing a std::vector<std::vector<int>>
   //  std::cout << printContainerV2(M) << std::endl; // not working !

}

For the moment it is ok if the code works only for std::vector type and up to one nested level (std::vector< std::vector>>). I am not sure it can be generic without efforts...

like image 304
coincoin Avatar asked Dec 21 '14 21:12

coincoin


2 Answers

Add #include <type_traits> header and replace your PrintContainerV2 with this:

template<typename T>
using if_not_cont = std::enable_if<!is_cont<T>::value>;

template<typename T>
using if_cont = std::enable_if<is_cont<T>::value>;

template <typename T, typename if_not_cont<T>::type* = nullptr>
std::string printContainerV2(T const& container)
{
    std::string str = "{";
    for (auto it = std::begin(container); it != std::end(container); ++ it)
        if (isLast(it, container))
                str = str + std::to_string(*it) + "}";
        else
                str = str + std::to_string(*it) + ",";
    return str;
}

template <typename T, typename if_cont<T>::type* = nullptr>
std::string printContainerV2(T const& container)
{
    std::string str = "{";
    for (auto it = std::begin(container); it != std::end(container); ++ it)
        if (isLast(it, container))
                str = str + printContainer(*it) + "}";
        else
                str = str + printContainer(*it) + ",";
    return str;
}
like image 88
Innocent Bystander Avatar answered Nov 04 '22 10:11

Innocent Bystander


The reason your solution doesn't work is here:

        if (is_cont<decltype(*it)>::value == true)
            str = str + printContainer(*it);
        else
            str = str + std::to_string(*it) + "}"; // <====

Even though the check you are making is compile time static - both branches of the if will be compiled anyway. The compiler will still try to evalute std::to_string(std::vector<T> ) and complain about that function not existing. What we need to do instead is use SFINAE: Substitution Failure Is Not An Error.

This is our version of print for containers:

template <typename T>
std::enable_if_t<is_cont<T>::value, std::string>
print(const T& container)
{
    // mostly the same as your code from printContainer() here, except instead of
    // std::to_string(*it), call print(*it).

    std::string str = "{";
    for (auto it = std::begin(container); it != std::end(container); ++ it)
    {
        str += print(*it);

        if (isLast(it, container)) {
            str += '}';
        }
        else {
            str += ',';
        }
    }

    return str;
}

And the non-container version:

template <typename T>
std::enable_if_t<!is_cont<T>::value, std::string>
print(const T& value)
{
    return std::to_string(value);
}

This is logically the same thing you were doing in your printContainerV2(), but this way the else branch - the to_string() one - won't get compiled for the actual container version.

like image 5
Barry Avatar answered Nov 04 '22 09:11

Barry