├── tests ├── header.h ├── file1.cpp ├── file2.cpp ├── run.sh ├── compile_tester.sh ├── compile.cpp └── unit.cpp ├── .travis.yml ├── LICENSE_1_0.txt ├── README.md └── concat.hpp /tests/header.h: -------------------------------------------------------------------------------- 1 | #include "../concat.hpp" 2 | 3 | void print(); -------------------------------------------------------------------------------- /tests/file1.cpp: -------------------------------------------------------------------------------- 1 | #include "header.h" 2 | 3 | int main() { 4 | print(); 5 | return 0; 6 | } -------------------------------------------------------------------------------- /tests/file2.cpp: -------------------------------------------------------------------------------- 1 | #include "header.h" 2 | #include 3 | 4 | void print() { 5 | theypsilon::concat<' '>("Test", "done."); 6 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | compiler: 3 | - clang 4 | - gcc 5 | before_install: 6 | - sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y 7 | - sudo apt-get update -qq 8 | - sudo apt-get install -qq libboost1.48-dev libyajl-dev libxml2-dev libxqilla-dev 9 | - if [ "$CXX" = "g++" ]; then sudo apt-get install -qq g++-4.8; fi 10 | - if [ "$CXX" = "g++" ]; then export CXX="g++-4.8" CC="gcc-4.8"; fi 11 | script: 12 | - tests/run.sh -------------------------------------------------------------------------------- /tests/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "" 3 | echo "RUNNING TEST SUITE" 4 | echo "==================" 5 | 6 | cd tests 7 | 8 | $CXX --version 9 | 10 | echo "" 11 | echo "UNIT TESTS" 12 | echo "----------" 13 | 14 | $CXX unit.cpp -std=c++11 -lm -lstdc++ 15 | ./a.out 16 | 17 | echo "LINKER TEST" 18 | echo "-----------" 19 | 20 | $CXX file1.cpp file2.cpp -std=c++11 -lm -lstdc++ 21 | ./a.out 22 | if [ $? -eq 0 ]; then 23 | echo -e "\e[1;32mPassed.\e[0m" 24 | fi 25 | rm a.out 26 | 27 | echo "" 28 | echo "COMPILER TESTS" 29 | echo "--------------" 30 | 31 | ./compile_tester.sh -------------------------------------------------------------------------------- /tests/compile_tester.sh: -------------------------------------------------------------------------------- 1 | number=0 2 | 3 | run_suite() { 4 | local exit_code=$1 5 | 6 | if [ $exit_code -eq 0 ]; then 7 | local test_suite=(`grep 'TEST_SUCCESS' compile.cpp | awk '{print $2}'`) 8 | else 9 | local test_suite=(`grep 'TEST_FAIL' compile.cpp | awk '{print $2}'`) 10 | fi 11 | for test in "${test_suite[@]}"; do 12 | echo $test >> output_compile.log 2>&1 13 | $CXX compile.cpp -std=c++11 -lm -lstdc++ -D"$test" >> output_compile.log 2>&1 14 | if [ $? -eq "$exit_code" ]; then 15 | echo -ne "\e[1;32m=\e[0m" 16 | else 17 | echo -e "\e[1;31m=" 18 | echo -e "Fail\t$test\e[0m" 19 | cat output_compile.log 20 | exit 1 21 | fi 22 | let number=number+1 23 | done 24 | } 25 | echo "" > output_compile.log 26 | run_suite 0 27 | let success=number 28 | run_suite 1 29 | let fail=number-success 30 | 31 | echo "" 32 | echo -e "\e[1;32mAll tests passed\e[0m ($success TEST_SUCCESS and $fail TEST_FAIL)" -------------------------------------------------------------------------------- /tests/compile.cpp: -------------------------------------------------------------------------------- 1 | #include "../concat.hpp" 2 | 3 | using namespace std; 4 | using namespace theypsilon; 5 | 6 | void run(...) {} 7 | 8 | #define RUN()\ 9 | void run(int yes) 10 | 11 | #ifdef TEST_FAIL_CUSTOM_TYPE 12 | struct Custom{}; 13 | 14 | RUN() { 15 | concat("nope", Custom{}); 16 | } 17 | #endif 18 | 19 | 20 | #ifdef TEST_SUCCESS_CUSTOM_TYPE 21 | struct Custom{}; 22 | 23 | std::ostream & operator<< (std::ostream &out, Custom const &t) { 24 | return out; 25 | } 26 | 27 | RUN() { 28 | concat("nope", Custom{}); 29 | } 30 | #endif 31 | 32 | #ifdef TEST_FAIL_CUSTOM_TYPE_WRONG_STREAM 33 | struct Custom{}; 34 | 35 | std::basic_ostream & operator<< (std::basic_ostream &out, Custom const &t) { 36 | return out; 37 | } 38 | 39 | alias type = decltype(concat("nope", Custom{})); 40 | #endif 41 | 42 | #ifdef TEST_SUCCESS_STRING 43 | RUN() { concat("yeah", string{}); } 44 | #endif 45 | 46 | #ifdef TEST_FAIL_CHAR_WSTRING 47 | RUN() { concat(wstring(L"nope")); } 48 | #endif 49 | 50 | #ifdef TEST_FAIL_ENDL1 51 | RUN() { concat(endl); } 52 | #endif 53 | 54 | #ifdef TEST_FAIL_ENDL2 55 | RUN() { concat("nope", endl); } 56 | #endif 57 | 58 | #ifdef TEST_FAIL_COUT1 59 | RUN() { concat(cout); } 60 | #endif 61 | 62 | #ifdef TEST_FAIL_COUT2 63 | RUN() { concat("nope", cout); } 64 | #endif 65 | 66 | int main(int argc, char* argv[]) { 67 | run(0); 68 | return 0; 69 | } -------------------------------------------------------------------------------- /LICENSE_1_0.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | concat.hpp [![Build Status](https://travis-ci.org/theypsilon/concat.svg?branch=master)](https://travis-ci.org/theypsilon/concat) 2 | ====== 3 | 4 | Because string concatenation deserves one-liners in C++11 too. 5 | 6 | ```cpp 7 | std::cout << concat("aa", "bb") << std::endl; 8 | /* output: "aabb" */ 9 | ``` 10 | 11 | 12 | That simple. **concat** also works with containers and other scalar types. 13 | 14 | ```cpp 15 | std::vector v{1,2,3,4,5}; 16 | std::cout << concat(v) << std::endl; 17 | /* output: "12345" */ 18 | ``` 19 | 20 | 21 | You may use separators in two ways: 22 | 23 | ```cpp 24 | std::cout << concat(separator(" + "), 1,2,3,4,5) << std::endl; 25 | /* output: "1 + 2 + 3 + 4 + 5" */ 26 | 27 | std::cout << concat<' '>('a','b','c') << std::endl; 28 | /* output: "a b c" */ 29 | ``` 30 | 31 | 32 | 33 | It is possible to mix between different parameter types, because under the hood we are using a ``std::ostringstream``. 34 | 35 | ```cpp 36 | std::cout << concat<' '>("hello", "world", std::make_tuple(1,2,3), '!', v) << std::endl; 37 | /* output: "hello world 1 2 3 ! 1 2 3 4 5" */ 38 | ``` 39 | 40 | 41 | 42 | Yeah, it also accepts tuples (even nested ones). You may also introduce manipulators. 43 | 44 | ```cpp 45 | std::cout << concat<' '>(std::setprecision(2), 4.0/3.0, std::setprecision(3), 1.0/3.0) << std::endl; 46 | /* output: "1.3 0.333" */ 47 | ``` 48 | 49 | 50 | 51 | And if you want fine-grained control of the underlying ``std::stringstream``, you may also supply it. Just make sure that you pass it as the first parameter (second, if there is also a separator parameter). 52 | 53 | ```cpp 54 | std::stringstream s; 55 | concat<' '>(s, "it", "just", "works!"); 56 | std::cout << s.str() << endl; 57 | /* output: "it just works!" */ 58 | ``` 59 | 60 | 61 | 62 | If you supply the std:stringstream as the second or any other parameter, it just gonna be converted to ``std::string``, so you are not writing on it. 63 | 64 | Supplying the ``std::stringstream`` can be useful. 65 | 66 | ```cpp 67 | std::ostringstream s1, s2; 68 | 69 | read_file(s2, "test.txt"); // this might cause s2.setstate(std::ios::failbit); 70 | 71 | try { 72 | s1.exceptions(std::ios::failbit); 73 | concat(s1, s2); // s1 gets the output of reading s2 74 | } catch(std::ios::failure& e) { 75 | std::cout << e.what() << std::endl; 76 | } 77 | 78 | /* output could be like this: "ios_base::clear: unspecified iostream_category error" */ 79 | ``` 80 | 81 | 82 | 83 | You can work with unicode, by specifing the char type as template parameter. 84 | 85 | ```cpp 86 | assert((concat( u"uni", u"code") == u"unicode") && 87 | (concat(separator(U""), U"Uni", U"code") == U"Unicode")); 88 | 89 | /* that's true! */ 90 | ``` 91 | 92 | 93 | By the way, the only way to specify a separator with UTF parameters is that one. 94 | 95 | String type conversion between different UTF charsets is not yet implemented, so when you choose an encoding format, you have to stick to it for all the supplied parameters. 96 | 97 | Know more 98 | ------ 99 | 100 | If you want to read more about the power of **concat**, you can learn all you need to know just by reading [tests/unit.cpp](tests/unit.cpp). 101 | 102 | Why not just use std::stringstream? 103 | ------ 104 | 105 | Of course you can, and I'm sure it is gonna be good enough for many situations. But sometimes I wish it could cover some other common use cases that right now require special (and often tedious) handling. Unfortunately, with ``std::stringstream`` you can not print **arrays**, **containers**, **tuples**, **pairs** or other **stringstreams** in a uniform and concise manner. 106 | 107 | Furthemore there are some tricky things about streams, that can make them feel a little unsafe. I.e.: when you attempt to add a ```(const char*)nullptr``` to a stream, it silently fails (unless you configured exceptions), and following operations with ``<<`` would totally be ignored. This would never happen with **concat** even if you decide to inject your own ``std::stringstream`` as shown above. 108 | 109 | Separators, and the function syntax, are also some nice additions that could help to produce a more terse and readable code. It is convenient to remember that in most of the code we write, we are not doing performance critical operations, and therefore we should always consider the chance of trading off a little performance penalty for a better readability. 110 | 111 | Build 112 | ------ 113 | It is just a header file! Just copy ``concat.hpp`` to your include path, maybe rename the namespace to something more convenient than my nickname, and start using it. 114 | 115 | Of course, also make sure your compiler is set to C++11 and that you are linking a standard library implementation in your project, because there is no other dependency. 116 | 117 | ---- 118 | 119 | Please **"Star"** the project on GitHub to help it to survive! Thanks! 120 | -------------------------------------------------------------------------------- /tests/unit.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include "catch.hpp" 3 | #include "../concat.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace theypsilon; 16 | using namespace std; 17 | 18 | template 19 | std::deque ilist(std::initializer_list list) { 20 | return list; 21 | } 22 | 23 | TEST_CASE( "Basic types, identity", "basic_id" ) { 24 | CHECK( concat(1) == "1"); 25 | CHECK( concat(1.0) == "1"); 26 | CHECK( concat('a') == "a"); 27 | CHECK( concat("a") == "a"); 28 | CHECK( concat(string("a")) == "a"); 29 | CHECK( concat(true) == "1"); 30 | } 31 | 32 | TEST_CASE( "Basic types, basic concat", "basic_c" ) { 33 | CHECK( concat(1,2,3,4,5) == "12345" ); 34 | CHECK( concat("1","2","3","4","5") == "12345" ); 35 | CHECK( concat('1','2','3','4','5') == "12345" ); 36 | CHECK( concat(1.0,2.0,3.0,4.0,5.0) == "12345" ); 37 | CHECK( concat('a','a') == "aa"); 38 | CHECK( concat("a","a") == "aa"); 39 | CHECK( concat(string("a"),string("a")) == "aa"); 40 | CHECK( concat(true, false, true, false) == "1010"); 41 | } 42 | 43 | 44 | TEST_CASE( "Basic types, separators", "basic_s" ) { 45 | CHECK( concat(separator(", "),1,2,3,4,5) == "1, 2, 3, 4, 5" ); 46 | CHECK((concat<',', ' '>(1,2,3,4,5)) == "1, 2, 3, 4, 5" ); 47 | CHECK( concat<','>(1,2,3,4,5) == "1,2,3,4,5" ); 48 | CHECK( concat(1,2,3,4,5) == "1\n2\n3\n4\n5" ); 49 | CHECK( concat(1,2,3,4,5) == "1 + 2 + 3 + 4 + 5" ); 50 | CHECK( concat<' '>("Hello", "World!") == "Hello World!" ); 51 | } 52 | 53 | TEST_CASE( "Basic types, mixed", "basic_m" ) { 54 | CHECK( concat(1,"2",3,"4",5,"6") == "123456" ); 55 | CHECK( concat("1",2,"3",4,"5",6) == "123456" ); 56 | CHECK( concat("a",2,3.0,'f') == "a23f" ); 57 | } 58 | 59 | TEST_CASE( "Pointer types, identity", "pointer_id" ) { 60 | string temp; 61 | CHECK( concat(static_cast(nullptr)) == ""); 62 | CHECK( concat(static_cast(nullptr)) == "0"); 63 | CHECK( concat(static_cast(&temp) ) != "0"); 64 | CHECK( concat(static_cast(nullptr)) == "0"); 65 | } 66 | 67 | TEST_CASE( "Container types, identity", "container_id" ) { 68 | CHECK( concat(vector{1,2,3,4,5}) == "12345" ); 69 | CHECK( concat(list{1,2,3,4,5}) == "12345" ); 70 | CHECK( concat(forward_list{1,2,3,4,5}) == "12345" ); 71 | CHECK( concat(deque{1,2,3,4,5}) == "12345" ); 72 | CHECK( concat(array{1,2,3,4,5}) == "12345" ); 73 | CHECK( concat(set{1,2,3,4,5}) == "12345" ); 74 | CHECK( concat(map{{1,2},{3,4},{5,6}}) == "123456" ); 75 | CHECK( concat(multiset{1,2,3,4,5}) == "12345" ); 76 | CHECK( concat(multimap{{1,2},{3,4},{5,6}}) == "123456" ); 77 | CHECK( concat(unordered_set{1,2,3,4,5}).size() == 5 ); 78 | CHECK( concat(unordered_map{{1,2},{3,4},{5,6}}).size() == 6 ); 79 | CHECK( concat(unordered_multiset{1,2,3,4,5}).size() == 5 ); 80 | CHECK( concat(unordered_multimap{{1,2},{3,4},{5,6}}).size() == 6 ); 81 | } 82 | 83 | TEST_CASE( "Array types, identity", "array" ) { 84 | int a[] = {1,2,3,4,5}; 85 | CHECK( concat(a) == "12345" ); 86 | } 87 | 88 | TEST_CASE( "Container type, mixed", "container_m" ) { 89 | vector v = {1,2,3,4,5}; 90 | 91 | CHECK( concat(1,2,3,4,5,v) == "1234512345" ); 92 | CHECK( concat(v,1,2,3,4,5) == "1234512345" ); 93 | CHECK( concat(1,2,3,v,4,5) == "1231234545" ); 94 | CHECK( concat(v,v) == "1234512345" ); 95 | CHECK( concat(v,1,v) == "12345112345" ); 96 | CHECK( concat(1,v,v,1) == "112345123451" ); 97 | CHECK( concat(1,v,1,v,1) == "1123451123451" ); 98 | CHECK( concat(v,v,v) == "123451234512345" ); 99 | CHECK( concat("something",v) == "something12345" ); 100 | CHECK( concat(v,"something") == "12345something" ); 101 | } 102 | 103 | TEST_CASE( "Container types, text identities and separators", "container_s" ) { 104 | vector s = {"hello"," ","world","!"}; 105 | CHECK( concat(s) == "hello world!" ); 106 | CHECK( concat<' '>(s) == "hello world !" ); 107 | 108 | vector c = {"hello"," ","world","!"}; 109 | CHECK( concat(c) == "hello world!" ); 110 | CHECK( concat<' '>(c) == "hello world !" ); 111 | 112 | vector ch = {'a','b','c'}; 113 | CHECK( concat(ch) == "abc" ); 114 | CHECK( concat<' '>(ch) == "a b c" ); 115 | } 116 | 117 | TEST_CASE( "Stream types, as host", "stream_host" ) { 118 | string temp; 119 | ostringstream s1; 120 | s1 << "hello"; 121 | temp = concat<' '>(s1, "world!"); 122 | CHECK ( temp == s1.str() ); 123 | REQUIRE( temp == "helloworld!"); 124 | CHECK ( concat(s1, 1,2,3) == "helloworld!123"); 125 | } 126 | 127 | TEST_CASE( "Stream types, as guest", "stream_guest" ) { 128 | string temp; 129 | ostringstream s1, s2; 130 | s1 << "hello"; 131 | s2 << "world!"; 132 | temp = concat<' '>(static_cast(s1), s2); 133 | CHECK( temp != s1.str() ); 134 | CHECK( temp == "hello world!"); 135 | CHECK( s1.str() == "hello"); 136 | } 137 | 138 | TEST_CASE( "Stream types, as host and guest", "stream_hg" ) { 139 | string temp; 140 | ostringstream s1, s2; 141 | s1 << "hello"; 142 | s2 << "world!"; 143 | temp = concat<' '>(s1, s2); 144 | CHECK ( temp == s1.str() ); 145 | REQUIRE( temp == "helloworld!"); 146 | CHECK ( concat(s1, 1,2,3, s1, s1) == "helloworld!123helloworld!123helloworld!123helloworld!123"); 147 | } 148 | 149 | TEST_CASE( "Stream types, mixed guest and host", "stream_m" ) { 150 | vector s = {"hello"," ","world","!"}; 151 | vector c = {"hello"," ","world","!"}; 152 | vector h = {'a','b','c'}; 153 | 154 | ostringstream s1, s2; 155 | s1 << "hello"; 156 | s2 << "world!"; 157 | REQUIRE( concat(s1, s, c, h, s2, "amazing") == "hellohello world!hello world!abcworld!amazing"); 158 | CHECK ( s1.str() == "hellohello world!hello world!abcworld!amazing"); 159 | } 160 | 161 | TEST_CASE( "Stream types, exception", "stream_exception" ) { 162 | stringstream s1, s2; 163 | s1.exceptions(ios::failbit | ios::eofbit | ios::badbit); 164 | 165 | SECTION("failbit") { 166 | s2.setstate(ios::failbit); 167 | CHECK_THROWS_AS( concat(s1,s2), ios::failure ); 168 | } 169 | 170 | SECTION("eofbit") { 171 | s2.setstate(ios::eofbit); 172 | CHECK_THROWS_AS( concat(s1,s2), ios::failure ); 173 | } 174 | 175 | SECTION("badbit") { 176 | s2.setstate(ios::badbit); 177 | CHECK_THROWS_AS( concat(s1,s2), ios::failure ); 178 | } 179 | 180 | SECTION("not cover") { 181 | s1.exceptions(ios::failbit); 182 | s2.setstate(ios::badbit); 183 | CHECK_NOTHROW( concat(s1,s2) ); 184 | 185 | s1.exceptions(ios::failbit); 186 | s2.setstate(ios::eofbit); 187 | CHECK_NOTHROW( concat(s1,s2) ); 188 | } 189 | 190 | SECTION("no throw") { 191 | s1.exceptions(ios::goodbit); 192 | 193 | s2.setstate(ios::failbit); 194 | CHECK_NOTHROW( concat(s1,s2) ); 195 | 196 | s2.setstate(ios::badbit); 197 | CHECK_NOTHROW( concat(s1,s2) ); 198 | 199 | s2.setstate(ios::eofbit); 200 | CHECK_NOTHROW( concat(s1,s2) ); 201 | } 202 | } 203 | 204 | TEST_CASE( "Null text types, mixed", "nulltext" ) { 205 | const char* msg = nullptr; 206 | CHECK( concat<' '>(msg) == ""); 207 | CHECK( concat<' '>("this is my message: ") == "this is my message: "); 208 | CHECK( concat("this is my message: ", msg) == "this is my message: "); 209 | CHECK( concat<' '>(msg, "this is my message:") == " this is my message:"); 210 | } 211 | 212 | TEST_CASE( "Null stream types, mixed", "nullstream" ) { 213 | stringstream s; 214 | CHECK( concat("",s) == ""); 215 | CHECK( concat("this is my message: ", s) == "this is my message: "); 216 | CHECK( concat<' '>("", s) == " "); 217 | } 218 | 219 | TEST_CASE( "Manipulators, mixed", "manipulators" ) { 220 | CHECK( concat<' '>(setprecision(2), 4.0/3.0, 1, 2) == "1.3 1 2"); 221 | CHECK( concat<' '>(setbase(16), 10, 16, 8) == "a 10 8"); 222 | CHECK( concat(setfill('-'), setw(5), 11) == "---11"); 223 | CHECK( concat(resetiosflags(std::ios::dec), 224 | setiosflags(std::ios::hex 225 | | std::ios::uppercase 226 | | std::ios::showbase),42) == "0X2A"); 227 | stringstream s; 228 | s.imbue(locale("en_US.utf8")); 229 | CHECK( concat(s, showbase, put_money(1200)) == "$12.00"); 230 | } 231 | 232 | TEST_CASE( "UTF types, identity", "utf_id" ) { 233 | CHECK( concat(L"wstring") == L"wstring" ); 234 | CHECK( concat(u"unicode") == u"unicode" ); 235 | CHECK( concat(U"Unicode") == U"Unicode" ); 236 | 237 | CHECK( concat(separator(L""), L"wstring") == L"wstring" ); 238 | CHECK( concat(separator(u""), u"unicode") == u"unicode" ); 239 | CHECK( concat(separator(U""), U"Unicode") == U"Unicode" ); 240 | } 241 | 242 | TEST_CASE( "UTF types, basic concat", "utf_c" ) { 243 | CHECK( concat(L"This is", L"wstring") == L"This iswstring" ); 244 | CHECK( concat(u"This is", u"unicode") == u"This isunicode" ); 245 | CHECK( concat(U"This is", U"Unicode") == U"This isUnicode" ); 246 | 247 | CHECK( concat(separator(L" "), L"This is", L"wstring") == L"This is wstring" ); 248 | CHECK( concat(separator(u" "), u"This is", u"unicode") == u"This is unicode" ); 249 | CHECK( concat(separator(U" "), U"This is", U"Unicode") == U"This is Unicode" ); 250 | } 251 | 252 | TEST_CASE( "initializer typed list, identity", "init_t_id" ) { 253 | auto&& a = {1,2,3,4,5}; 254 | const auto& b = {1,2,3,4,5}; 255 | auto c = {1,2,3,4,5}; 256 | CHECK( concat(a) == "12345" ); 257 | CHECK( concat(b) == "12345" ); 258 | CHECK( concat(c) == "12345" ); 259 | CHECK( concat(initializer_list{1,2,3,4,5}) == "12345" ); 260 | 261 | using inplace = decltype(initializer_list{1,2,3,4,5}); 262 | static_assert(is_same&&>::value, ""); 263 | //static_assert(is_same&>::value, ""); works in clang but fails in gcc 264 | static_assert(is_same>::value, ""); 265 | static_assert(is_same>::value, ""); 266 | } 267 | 268 | TEST_CASE( "initializer non-inferred list, identity", "init_t_id" ) { 269 | CHECK( concat(ilist({1,2,3,4,5})) == "12345" ); 270 | CHECK( concat(separator(", "), ilist({1,2,3,4,5})) == "1, 2, 3, 4, 5" ); 271 | } 272 | 273 | TEST_CASE( "tuple, identity", "tuple_id" ) { 274 | CHECK( concat(make_tuple(1,2,3,4,5)) == "12345" ); 275 | CHECK( concat<' '>(make_tuple("hello","world!")) == "hello world!" ); 276 | } 277 | 278 | TEST_CASE( "tuple, nested", "tuple_nested" ) { 279 | CHECK( concat(make_tuple(1,2,3,make_tuple(4,5))) == "12345" ); 280 | CHECK( concat<' '>(make_tuple("hello",make_tuple("world!"))) == "hello world!" ); 281 | CHECK( concat<' '>(make_tuple(make_tuple("hello", "my"),make_tuple("world!"))) == "hello my world!" ); 282 | } 283 | 284 | TEST_CASE( "tuple, mixed", "tuple_mixed" ) { 285 | CHECK( concat(make_tuple(1,2,3,4),5) == "12345" ); 286 | CHECK( concat(make_tuple(1,2,3),make_tuple(4,5)) == "12345" ); 287 | CHECK( concat<' '>(make_tuple("hello","world!"), "goodbye", "friend!") == "hello world! goodbye friend!" ); 288 | } 289 | 290 | TEST_CASE( "pair, identity", "pair_id" ) { 291 | CHECK( concat(make_pair(1,2)) == "12" ); 292 | CHECK( concat<' '>(make_pair("hello","world!")) == "hello world!" ); 293 | } 294 | 295 | TEST_CASE( "pair, nested", "pair_nested" ) { 296 | CHECK( concat(make_pair(1,make_pair(2,3))) == "123" ); 297 | CHECK( concat<' '>(make_pair("hello",make_pair("world","!"))) == "hello world !" ); 298 | CHECK( concat<' '>(make_pair(make_pair("hello", "my"),make_pair("world", "!"))) == "hello my world !" ); 299 | } 300 | 301 | TEST_CASE( "pair, mixed", "pair_mixed" ) { 302 | CHECK( concat(make_pair(1,2),3) == "123" ); 303 | CHECK( concat(make_pair(1,2),make_pair(3,4)) == "1234" ); 304 | CHECK( concat<' '>(make_pair("hello","world!"), "goodbye", "friend!") == "hello world! goodbye friend!" ); 305 | } 306 | 307 | TEST_CASE( "README.md", "readme") { 308 | CHECK( concat("aa", "bb") == "aabb" ); 309 | 310 | std::vector v{1,2,3,4,5}; 311 | CHECK( concat(v) == "12345" ); 312 | 313 | CHECK( concat(separator(" + "), 1,2,3,4,5) == "1 + 2 + 3 + 4 + 5" ); 314 | 315 | CHECK( concat<' '>('a','b','c') == "a b c" ); 316 | 317 | CHECK( concat<' '>("hello", "world", std::make_tuple(1,2,3), '!', v) 318 | == "hello world 1 2 3 ! 1 2 3 4 5" ); 319 | 320 | CHECK( concat<' '>(std::setprecision(2), 4.0/3.0, std::setprecision(3), 1.0/3.0) == "1.3 0.333" ); 321 | 322 | std::stringstream s; 323 | concat<' '>(s, "it", "just", "works!"); 324 | CHECK( s.str() == "it just works!" ); 325 | 326 | std::ostringstream s1, s2; 327 | s2.setstate(std::ios::failbit); 328 | const char* error = nullptr; 329 | try { 330 | s1.exceptions(std::ios::failbit); 331 | concat(s1, s2); // s1 gets the output of reading s2 332 | } catch(std::ios::failure& e) { 333 | error = e.what(); 334 | } 335 | CHECK( error != nullptr ); 336 | 337 | bool assertion = (concat( u"uni", u"code") == u"unicode") && 338 | (concat(separator(U""), U"Uni", U"code") == U"Unicode"); 339 | CHECK(assertion); 340 | } 341 | 342 | template 343 | struct UserDefinedType { 344 | const CharT* text; 345 | UserDefinedType(const CharT* text) : text{text} {} 346 | friend std::basic_ostream & operator<< (std::basic_ostream &out, UserDefinedType const &t) { 347 | out << t.text; 348 | return out; 349 | } 350 | }; 351 | 352 | TEST_CASE( "User defined type overload, mixed", "user" ) { 353 | CHECK( concat(UserDefinedType("UserDefinedType")) == "UserDefinedType" ); 354 | CHECK( concat<' '>("my", UserDefinedType("UserDefinedType"), "!") == "my UserDefinedType !" ); 355 | CHECK( concat(UserDefinedType(L"UserDefinedType")) == L"UserDefinedType" ); 356 | CHECK( concat(UserDefinedType(u"UserDefinedType")) == u"UserDefinedType" ); 357 | CHECK( concat(UserDefinedType(U"UserDefinedType")) == U"UserDefinedType" ); 358 | } -------------------------------------------------------------------------------- /concat.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CONCAT 3 | * Version: 2014-07-29 4 | * ---------------------------------------------------------- 5 | * Copyright (c) 2014 José Manuel Barroso Galindo. All rights reserved. 6 | * 7 | * Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | */ 10 | #ifndef THEYPSILON_CONCAT 11 | #define THEYPSILON_CONCAT 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace theypsilon { // rename this to something that fits your code 19 | 20 | template 21 | struct separator_t { // this class shouldn't be explicitly invoked in client code, use "separator" instead 22 | const CharT* sep; 23 | constexpr explicit separator_t(const CharT* s) noexcept: sep{s} {} 24 | }; 25 | 26 | template 27 | constexpr separator_t separator(const CharT* s) { 28 | return separator_t(s); 29 | } 30 | 31 | namespace sep { // this can be used as an additional way of defining a separator, check 3. entry point 32 | constexpr char none [] = ""; 33 | constexpr char space[] = " "; 34 | constexpr char endl [] = "\n"; 35 | constexpr char comma[] = ", "; 36 | constexpr char plus [] = " + "; 37 | }; 38 | 39 | namespace { // type helpers and traits 40 | template 41 | struct is_writable_stream : std::integral_constant>::value || 43 | std::is_same>::value || 44 | std::is_same>::value>{}; 45 | 46 | template 47 | struct is_stringstream : std::integral_constant>::value || 49 | std::is_same>::value || 50 | std::is_same>::value>{}; 51 | 52 | template 53 | struct is_c_str : std::integral_constant::type, CharT const *>::value || 55 | std::is_same::type, CharT *>::value>{}; 56 | 57 | template 58 | struct is_char_sequence : std::integral_constant::value || 60 | is_c_str::value || 61 | is_c_str::value || 62 | is_c_str::value>{}; 63 | 64 | template 65 | struct is_string : std::integral_constant::value || 67 | std::is_same::value || 68 | std::is_same::value || 69 | std::is_same::value>{}; 70 | 71 | struct can_const_begin_end_impl { 72 | template())), 73 | typename E = decltype(std::end (std::declval()))> 74 | static std::true_type test(int); 75 | template 76 | static std::false_type test(...); 77 | }; 78 | 79 | template 80 | struct can_const_begin_end : public decltype(can_const_begin_end_impl::test(0)) {}; 81 | 82 | template 83 | struct is_iterable : std::integral_constant::value && 85 | !is_string::value && !is_stringstream::value && !is_char_sequence::value>{}; 86 | 87 | template 88 | struct does_overload_ostream_impl { 89 | template&>() 90 | << std::declval())> 91 | 92 | static std::true_type test(int); 93 | template 94 | static std::false_type test(...); 95 | }; 96 | 97 | template 98 | struct does_overload_ostream : public decltype(does_overload_ostream_impl::template test(0)) {}; 99 | 100 | template 101 | struct is_parametrized_manipulator : std::integral_constant()))>::value || 103 | std::is_same()))>::value || 104 | std::is_same()))>::value || 105 | std::is_same()))>::value || 106 | std::is_same()))>::value || 107 | std::is_same()))>::value>{}; 108 | 109 | template 110 | struct is_manipulator : std::integral_constant::value || is_parametrized_manipulator::value) 112 | && does_overload_ostream::value>{}; 113 | 114 | template class Template> 115 | struct is_specialization_of : std::false_type {}; 116 | 117 | template