【C++】print関数の作り方【coutをラップした万能出力関数】

C++でオーバーロードや可変長引数に対応した万能print関数を作ります。これでstd::cout << 9 << std::endl;の面倒な記述ともおさらばです。

まずは基本的なprint関数の自作方法です。std::coutをラップしているため、様々な型の出力に対応できます。

#include <iostream>

template<class T> void print(const T& value) {
  std::cout << value << std::endl;
}

int main() {
  print("a"); // 出力結果: "a\n"
  print(999); // 出力結果: "999\n"
}

続いて、可変長引数に対応したprint関数です。

// clang++ -std=c++11 print.cpp
#include <iostream>

void print() { std::cout << std::endl; }
template<class T, class... A> void print(const T& first, const A&... rest) { std::cout << first; print(rest...); }
template<class... A> void print(const A&... rest) { print(rest...); }

int main(int argc, char *argv[]) {
  print();              // ""
  print("a");           // "a"
  print("b", "c");      // "bc"
  print("d", "e", "f"); // "def"
  print('C', 99);       // "C99"
  print(3.14);          // "3.14"
}

独自クラスに対応

独自クラスの出力に対応したい場合は、以下の手順で<<演算子をオーバーロードします。

class A {
  int a = 2, b = 9;
  friend std::ostream& operator<<(std::ostream& os, const A& it) {
    return os << it.a + it.b;
  }
};
print("C", A()); // "C11"

カンマ区切り

カンマ区切りが欲しい場合は以下のコードも合わせてご利用ください。

template<class... A> void prints()      { std::cout << std::endl; }
template<class... A> void prints_rest() { std::cout << std::endl; }
template<class T, class... A> void prints_rest(const T& first, const A&... rest) { std::cout << ", " << first; prints_rest(rest...); }
template<class T, class... A> void prints(const T& first, const A&... rest)      { std::cout << first; prints_rest(rest...); }
prints("a");           // "a"
prints("b", "c");      // "b, c"
prints("d", "e", "f"); // "d, e, f"
prints('C', 99);       // "C, 99"
prints(3.14);          // "3.14"
prints();              // ""

関数オブジェクト版

print関数を関数オブジェクトとして定義することも可能です。

struct Print {
   template<class T> void operator()(const T& v) { std::cout << v << std::endl; }
   template<class T> void operator ,(const T& v) { std::cout << v << std::endl; }
// template<class F> auto hook(F f) { return [=](auto v) { std::cout << static_cast<decltype(v)>(f(v)) << std::endl; }; }
// auto terminator(const char *s) { return [=](auto v) { std::cout << v << s << std::flush; }; }
};

printという単一の名前に様々な機能を持たせることが可能となります。

// clang++ print.cpp

auto print = Print{};
print("hello");
print, "world";

auto s = "ibm";
std::for_each(s, s + 3, print); // 'i', 'b', 'm'

// clang++ -std=c++14 print.cpp

std::for_each(s, s + 3, print.hook(toupper)); // 'I', 'B', 'M'
std::for_each(s, s + 3, print.hook([](auto v) { return v - 1; })); // 'h', 'a', 'l'

auto p = print.terminator("\r\n");
p("a"); // 出力: "a\r\n"
p("b"); // 出力: "b\r\n"

ヘルパークラスを定義したり、クラス側で内部で状態を持たせたりすれば、より汎用的な機能を作り上げることも可能です。いろいろと実験してみると良いでしょう。

struct Print {
  const char *_sep = 0, *_trm = 0, *_fmt = 0;
  template<class... T> void _print() { if (_trm) std::cout << _trm << std::flush; else std::cout << std::endl; }
  template<class... T> void  print() { _print(); }
  template<class T, class... A> void _print(const T& v, const A&... a) { if (_sep) std::cout << _sep; std::cout << v; _print(a...); }
  template<class T, class... A> void  print(const T& v, const A&... a) { if (_fmt) printf(_fmt, _to_p(_to_s(v)), _to_p(_to_s(a))...); else { std::cout << v; _print(a...); } }
  template<class... T> auto operator()(const T&... v) { print(v...); return *this; }
  template<class... T> auto operator ,(const T&... v) { print(v...); return *this; }
  auto terminator(const char* s) { return Print{_sep, s      }; }
  auto separator( const char* s) { return Print{s   , _trm   }; }
  auto format(    const char* s) { return Print{_sep, _trm, s}; }
  template<class F> auto hook(F f) { return [=](auto&& v) { (*this)(static_cast<decltype(v)>(f(std::forward<decltype(v)>(v)))); }; }
  template<class F> auto pipe(F f) { return [=](auto&& v) { (*this)(f(std::forward<decltype(v)>(v))); }; }
private:            static auto _to_p(const std::string& v) { return v.c_str(); }
  template<class T> static auto _to_p(const T& v)           { return v;         }
  template<class T, std::enable_if_t< std::is_class<T>::value, int> = 0> static auto _to_s(const T& v) { return (std::stringstream{} << v).str(); }
  template<class T, std::enable_if_t<!std::is_class<T>::value, int> = 0> static auto _to_s(const T& v) { return v;                                }
};
auto p = Print{};
p("a", "b");                         // "ab"
p, "a", "b";                         // "a\nb"
p.format("%s %d\n")("Shop", 99);     // "Shop 99"
p.format("STORE %d\n"), 100, 108;    // "STORE 100", "STORE 108"
p.terminator("'s\n")(8, 0);          // "80's"
p.separator("-")("a", 'b', 99);      // "a-b-99"
Print{"_", ""}("a", "b"), "c", "\n"; // "a_bc"
p.separator(", ")("a", "b");         // "a, b"
p.hook(toupper)('a');                // "A"

using namespace std;
auto toupper = ::toupper; // auto toupper = [](int c) -> int { return std::toupper(c); };
auto s = "ibm";

for_each(s, s + 3, p.hook(toupper)); // 'I', 'B', 'M'
for_each(s, s + 3, p.pipe(toupper)); // 73, 66, 77
auto v = std::vector<int>{73, 66, 77};
for_each(v.begin(), v.end(), p.pipe([](auto i) {
  return char(i - 1);
}));                                 // 'H', 'A', 'L'

p, // 表示結果: "bc"
std::string("abc").substr(1);
広告
広告