简体   繁体   中英

How to print a template to console in C++17?

I have this base template class:

template<typename Type>
struct InputBase {
    virtual void DisplaySingleResult(Type const& res) const {
        std::cout << res << std::endl;
    }
};

Therefore, this code below doesn't compile, because vector doesn't have a << operator

struct Input : InputBase<std::vector<std::vector<int>>> {
    void DisplaySingleResult(std::vector<std::vector<int>> const& results) const override {
        for (std::vector<int> const& result : results) {
            std::cout << "[" << result[0] << "," << result[1] << "]";
        }
        std::cout << std::endl;
    }
};

When compiling, the method InputBase::DisplaySingleResult fails to compile because it uses vector type.

A workaround is to make the virtual InputBase::DisplaySingleResult method pure virtual.

However, I want to have a DisplaySingleResult method in my base class, with the same signature, to avoid rewriting all other derived classes. How to do that in C++17?

Solution tentative: in InputBase::DisplaySingleResult , check if the template Type has a << operator before using it.

I do not recommend to provide an overload for std::ostream& operator<<(std::ostream&,const std::vector<T>&) directly (not sure if it is even allowed). But indirectly.

Instead of calling std::cout << res << std::endl; you can call a function:

template<typename Type>
void print_result(const Type& res) { std::cout << res << std::endl; }

template<typename Type>
struct InputBase {
    virtual void DisplaySingleResult(Type const& res) const {
        print_result(res);
    }
};

Now you can provide overloads for any type that does not have a operator<< , eg

void print_result(const std::vector<int>& v) { for (const auto& e : v) print_result(e); } 

To be more flexible with specializations you might want to consider to make print_result a class template rather than function template. For example there could be a specialization that calls print_result on the elements when T has begin() and end() .

You could test at compile-time if the type is supported appropriately:

  1. If there's a suitable output operator available already use it (this test assures that you don't use iterators if the type can be output directly, like eg std::string ).
  2. Otherwise if the type has begin and end functions use these.
  3. Otherwise we are in an error condition!

This might look as follows:

template<typename Type>
struct InputBase
{
    static void displaySingleResult(Type const& res)
    {
        if constexpr (decltype(has_operator_out(res))::value)
        {
            std::cout << res;
        }
        else if constexpr
        (
            decltype(has_begin(res))::value && decltype(has_end(res))::value
        )
        {
            // your own desired formatting, e.g.:
            std::cout << '[';
            for(auto& r : res)
            {
                std::cout << r << ' ';
            }
            std::cout << ']';
        }
        else
        {
            // error handling:
            std::cout << "<invalid!>";
        }
    }

private:
    template <typename T>
    static auto has_operator_out(T& t)
        -> decltype(std::cout << t, std::true_type());
    static std::false_type has_operator_out(...);
    template <typename T>
    static auto has_begin(T& t)
        -> decltype(t.begin(), std::true_type());
    static std::false_type has_begin(...);
    template <typename T>
    static auto has_end(T& t)
        -> decltype(t.end(), std::true_type());
    static std::false_type has_end(...);
};

Recursively calling the function instead comes allows to print nested containers as well (thanks 463035818_is_not_a_number for the hint):

std::cout << '[';
for(auto& r : res)
{
    InputBase<std::remove_cv_t<std::remove_reference_t<decltype(r)>>>
        ::displaySingleResult(r);
    std::cout << ' ';
}
std::cout << ']';

Note that:

  1. ... the function can be static as this is not used anywhere.
  2. ... defining an operator<< instead would be more C++ like.
  3. ... you might provide further tests or adjust the existing ones to your needs, eg for using std::begin and std::end instead.
  4. ... there are other patterns possible for these tests, you might want to play around a bit with ;)

If you need to have DisplaySingleResult() to be virtual, a typical way is to delegate to a non-virtual function, which can be a template:

virtual void DisplaySingleResult(Type const& res)
{
    DisplaySingleResultImpl(res);
}

Then you can templetize DisplaySingleResultImpl() . Now, in many cases, you don't want to do that in the base class (or only want to provide a typical implementation) - in these cases, CRTP comes to mind:

template<typename Type, typename Derived>
struct InputBase {
    virtual void DisplaySingleResult(Type const& res) const {
        static cast<const Derived&>(*this).DisplaySingleResultImpl(res);
    }
};

template<typename Type, typename Derived>
struct InputBaseWithCoutPrint : InputBase<Type, Derived> {
    void DisplaySingleResultImpl(Type const& res) const {
        std::cout << res << std::endl;
    }
};

CRTP is a very powerful tool, don't overuse it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM