Let's suppose I have the following class in an header file header.h
:
#pargma once
#include <type_traits>
#include <iostream>
#include <sstream>
struct foo
{
// Utils struct
template <class T, class... Ts>
struct is_any: std::disjunction <std::is_same <T, Ts>... >{};
// Standard case
template <class T_os, class T, class... Args, typename = std::enable_if_t<is_any
<T_os, std::ostream, std::ostringstream>::value>>
const foo& operator () ( T_os& os, const T& first, const Args&... args ) const { os << "hello"; return *this; }
// Default std::ostream = std::cout case
template <class T, class... Args>
const foo& operator () ( const T& first, const Args&... args ) const { std::cout << "hello"; return *this; }
};
I defined a struct
in which I overloaded the ()
operator two times: in the "standard case" the template is enabled if the T_os
type is one of this list (std::ostream
, std::ostringstream
) and a message is sent to output using the T_os os
object. In the "Default std::ostream = std::cout case" the template is called if T_os
is not explicitly present and a message is sent to output using the std::ostream std::cout
object.
A simple usage in main is:
#include "header.h"
int main()
{
foo foo_obj;
// Standard case
foo_obj ( std::cout, "first", "second" );
// Default std::ostream = std::cout case
foo_obj ( "first", "second" );
}
I want to know if it would be possible to merge the "standard case" operator ()
overload within the "Default std::ostream = std::cout case" operator ()
overload, in order to be able to perform the two operations shown in main
using only an operator ()
overload instead of two. Thanks.
CodePudding user response:
You could make operator()
a front-end for the real implementation. You can then make it forward the arguments to the real implementation and add std::cout
if needed.
Example:
struct foo {
template <class T, class... Args>
const foo& operator()(T&& first, Args&&... args) const {
if constexpr (std::is_base_of_v<std::ostream, std::remove_reference_t<T>>) {
// or std::is_convertible_v<std::remove_reference_t<T>*, std::ostream*>
// first argument is an `ostream`, just forward everything as-is:
op_impl(std::forward<T>(first), std::forward<Args>(args)...);
} else {
// add `std::cout` first here:
op_impl(std::cout, std::forward<T>(first), std::forward<Args>(args)...);
}
return *this;
}
private:
// implement the full operator function here. `os` is an `ostream` of some sort:
template <class S, class... Args>
void op_impl(S&& os, Args&&... args) const {
(..., (os << args << ' '));
os << '\n';
}
};
I used is_base_of_v<std::ostream, ...>
instead of is_same
to make it use any ostream
(like an ostringstream
or ofstream
) if supplied as the first argument.
CodePudding user response:
Maybe a solution with constexpr if
(see here) and fold expressions will help.
Something like for example the below.
#include <type_traits>
#include <iostream>
#include <sstream>
template <class TFirst, class... TsRest>
void print(TFirst&& first, TsRest&& ... rest) {
if constexpr (std::is_same_v <std::remove_cvref_t<TFirst>, std::ostringstream> or
std::is_same_v <std::remove_cvref_t<TFirst>, std::ostream>) {
((first << rest << ' '), ...);
}
else {
std::cout << first << ' ';
((std::cout << rest << ' '), ...);
}
}
int main() {
print(1, 2);
std::ostringstream oss{};
print(oss, 3, 4);
std::cout << "\n\n" << oss.str() << "\n\n";
}
Maybe, this could give you an idea . . .