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);