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.
CodePudding user response:
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()
.
CodePudding user response:
You could test at compile-time if the type is supported appropriately:
- 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 e.g.
std::string
). - Otherwise if the type has
begin
andend
functions use these. - 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:
- ... the function can be
static
asthis
is not used anywhere. - ... defining an
operator<<
instead would be more C like. - ... you might provide further tests or adjust the existing ones to your needs, e.g. for using
std::begin
andstd::end
instead. - ... there are other patterns possible for these tests, you might want to play around a bit with ;)
CodePudding user response:
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.