Home > Back-end >  "Merge" two signatures of operator () overload into one in a template class, how?
"Merge" two signatures of operator () overload into one in a template class, how?

Time:07-25

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';
    }
};

Demo

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 . . .

  • Related