├── README.md ├── fasttuple.h ├── license.txt └── tuplegen.cpp /README.md: -------------------------------------------------------------------------------- 1 | # CFastTuple 2 | 3 | This single header provides two alternate implementations of a replacement for std::tuple, which is both faster and easier to use. 4 | 5 | When I first starting using the tuple+structured binding pattern for functions with multiple returns, I assumed it was identical to an unnamed structure in 6 | its code generation and that I was basically paying nothing for it. However, when I dug more closely, this wasn't true. 7 | 8 | In actuality, std::tuple does not implement an anonymous struct with member variables based on the template parameters. This is because c++20 does not allow 9 | expanding variadic template arguments to define structure members, and would give you no way to give the members unique names if you could. So, std::tuple is instead 10 | implemented as a nested structure, with each nesting level defining one member variable. 11 | 12 | This has several implications for code generation, both for optimized code, and debuggable code, and API: 13 | - This will cause arguments that would have been passed/returned in registers to instead be passed on the stack, in multiple scenarios. This applies to both GCC and MSVC. 14 | - Nesting past a certain level will cause MSVC to never keep the struct in registers. 15 | - Fast field access, fast initialization, etc are all based on relying on inlining by the optimizer. This makes debug builds slow, and may also break down past some nesting level. 16 | - When viewed in the debugger, instead of seeing a simple struct, you will see a nested struct (unless you are using a debug visualizers to neaten up the display). 17 | - You must access fields using a non-member get function (std::get) 18 | - The member variables are stored in memory in the opposite order of what you would expect. This is both confusing, and makes the struct layout incompatible with an identical named struct. 19 | - THe more items in an std::tuple, the deeper the template nesting level is, exposing the possibility of running into compiler limits or asymptotically worsening compile times on large tuples. 20 | 21 | The only other way to implement an std::tuple class would have been to define one template for each number of arguments. So, that's what I did, using a simple 22 | program to generate the output .h file. 23 | 24 | This .h file implements 2 classes, CTuple and CCompatibleTuple. The difference between them is that CTuple defines the member variables in the same order as the type 25 | template arguments, while CCompatibleTuple defines them in the opposite order, giving it a memory layout compatible with std::tuple, albeit at a slight cost in debug code. Because of 26 | the order layout in CTuple being forward, it is able to initialize the struct without defining constructors - just using brace initialization. This is much better for debug code generation 27 | and static initialization. OTOH CCompatibleTuple has enhanced compatibility with std::tuple because of having the same memory layout. This allows, for instance, passing a CCompatibleTuple 28 | to a function expecting a reference to an std::tuple. 29 | 30 | # API 31 | These classes follow the basic api of std::tuple, including deduction guides. You can access fields using std::get, construct them the same way, etc. In addition there are several convenience APIs: 32 | 33 | - You can use a member Get\() or Get\() to access the fields. 34 | - You can also conveniently access the fields as _0, _1, ... This has the advantage of generating good code in debug builds without relying on inlining. 35 | - compiler dependent methods of forcing inlining even in debug builds are used in member acccess. Note that you will need to set /Ob1 in oyur debug build for MSVC to take advantage of this. 36 | 37 | # TupleGen.cpp 38 | Fasttuple.H was generated by this simple portable c++ program. The program has multiple options which may be used to customize the header file. 39 | 40 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | MIT No Attribution 2 | 3 | Copyright 2022, Casual Numerics, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 13 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 14 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 15 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /tuplegen.cpp: -------------------------------------------------------------------------------- 1 | // Sorry for the low code quality of this. I have great libraries for formatting text, parsing args, etc. but 2 | // I wanted this to be standalone and build with any c++ compiler w/o dependencies. 3 | // I wrote this about as fast as I could type (which is very fast), and every time I thought about a better way 4 | // express something, ignored the thought and kept typing :-) 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | static std::string s_sCurrentIndentString; 12 | static int s_nCurrentIndentSize = 0; 13 | static int s_nIndentStep = 1; 14 | static int s_nMaxArgs = 12; 15 | static char s_cIndentChar = '\t'; 16 | 17 | static std::string s_sTupleClassName = "CTuple"; 18 | static std::string s_sCompatibleTupleClassName = "CCompatibleTuple"; 19 | static std::string s_sMemberGetName = "Get"; 20 | static std::string s_sNameSpace; 21 | 22 | static std::string s_sConstExpr = "constexpr "; // set to empty if no constexpr desired 23 | 24 | std::unordered_map > parseTable = 25 | { 26 | { "maxargs", &s_nMaxArgs }, 27 | { "indent_step", &s_nIndentStep }, 28 | { "classname", &s_sTupleClassName }, 29 | { "compatclassname", &s_sCompatibleTupleClassName }, 30 | { "membergetname", &s_sMemberGetName }, 31 | { "namespace", &s_sNameSpace } 32 | }; 33 | 34 | void Fail( char const *pMsg ) 35 | { 36 | printf( "fatal error %s\n arguments are:\n", pMsg ); 37 | for( auto entries : parseTable ) 38 | { 39 | printf( " %s\n", entries.first.c_str() ); 40 | } 41 | exit( 1 ); 42 | } 43 | 44 | void CheesyArgumentParser( int argc, char **argv ) 45 | { 46 | 47 | for( int nArg = 1; nArg < argc; nArg++ ) 48 | { 49 | char const *pArg = argv[nArg]; 50 | if ( pArg[0] != '-' ) 51 | { 52 | Fail( "unrecognized arg" ); 53 | } 54 | while( pArg[0] == '-' ) 55 | { 56 | pArg++; // let them type -arg or --arg if they want 57 | } 58 | auto findIt = parseTable.find( pArg ); 59 | if ( findIt == parseTable.end() ) 60 | { 61 | Fail( "unrec arg" ); 62 | } 63 | std::variant varPtr = findIt->second; 64 | switch( varPtr.index() ) 65 | { 66 | case 0: // string arg 67 | { 68 | if ( nArg == argc -1 ) 69 | { 70 | Fail( "arg missing argument" ); 71 | } 72 | *( std::get<0>( varPtr ) ) = argv[++nArg]; 73 | } 74 | break; 75 | 76 | case 1: // int 77 | { 78 | if ( nArg == argc -1 ) 79 | { 80 | Fail( "arg missing int argument" ); 81 | } 82 | *( std::get<1>( varPtr )) = atoi( argv[++nArg] ); // no error check, don't care. 83 | } 84 | break; 85 | 86 | case 2: // bool, set var to true if found 87 | { 88 | *( std::get<2>( varPtr ) ) = true; 89 | } 90 | break; 91 | } 92 | } 93 | } 94 | 95 | 96 | void AdjustIndent( int nNumStopsDelta ) 97 | { 98 | s_nCurrentIndentSize += nNumStopsDelta; 99 | s_sCurrentIndentString.clear(); 100 | for( int i = 0; i < s_nIndentStep * s_nCurrentIndentSize; i++ ) 101 | { 102 | s_sCurrentIndentString.push_back( s_cIndentChar ); 103 | } 104 | 105 | } 106 | 107 | void Print( char const *pString ) 108 | { 109 | printf( "%s", pString ); 110 | } 111 | 112 | void Print( std::string const &str ) 113 | { 114 | Print( str.c_str() ); 115 | } 116 | 117 | void Print( int x ) 118 | { 119 | printf( "%d", x ); 120 | } 121 | 122 | void Print( char c ) 123 | { 124 | printf( "%c", c ); 125 | } 126 | 127 | template 128 | void Print( const ArgTypes&... values ) 129 | { 130 | ( Print( values ), ...); 131 | } 132 | 133 | template 134 | void PrintLn( const ArgTypes&... values ) 135 | { 136 | Print( s_sCurrentIndentString ); 137 | ( Print( values ), ...); 138 | printf( "\n" ); 139 | } 140 | 141 | void PrintTupleDef( int nNumFields, std::string const &className, bool bUseCompatibleLayout ) 142 | { 143 | PrintLn( "template class ", className, ";\n" ); 144 | std::string sTemplateArgs = "<"; 145 | std::string sTypeList = ""; 146 | for( int nArg = 0; nArg < nNumFields; nArg++ ) 147 | { 148 | sTemplateArgs += "typename Type"; 149 | if ( nArg > 9 ) 150 | { 151 | sTemplateArgs.push_back( '0' + ( nArg / 10 ) ); 152 | } 153 | sTemplateArgs.push_back( '0' + ( nArg % 10 ) ); 154 | sTemplateArgs += "_t"; 155 | sTypeList += "Type"; 156 | if ( nArg > 9 ) 157 | { 158 | sTypeList.push_back( '0' + ( nArg / 10 ) ); 159 | } 160 | sTypeList.push_back( '0' + ( nArg % 10 ) ); 161 | sTypeList += "_t"; 162 | 163 | if ( nArg != nNumFields -1 ) 164 | { 165 | sTemplateArgs += ", "; 166 | sTypeList += ","; 167 | } 168 | } 169 | sTemplateArgs += ">"; 170 | 171 | if ( s_sNameSpace.size() ) 172 | { 173 | PrintLn( "namespace ", s_sNameSpace ); 174 | PrintLn( "{" ); 175 | AdjustIndent( 1 ); 176 | } 177 | 178 | PrintLn( "template ", sTemplateArgs ); 179 | PrintLn( "class ", className, "<", sTypeList, ">" ); 180 | PrintLn( "{" ); 181 | PrintLn( "public:" ); 182 | AdjustIndent( 1 ); 183 | if ( bUseCompatibleLayout ) // put the fields in reverse order? 184 | { 185 | for( int i = nNumFields -1; i >= 0; i-- ) 186 | { 187 | PrintLn( "Type", i, "_t _", i, ';' ); 188 | } 189 | } 190 | else 191 | { 192 | for( int i = 0; i < nNumFields; i++ ) 193 | { 194 | PrintLn( "Type", i, "_t _", i, ';' ); 195 | } 196 | } 197 | 198 | if ( bUseCompatibleLayout ) 199 | { 200 | // if fields are stored in reverse order, generate constructors so you can give them in the right order like STL tuple 201 | for( char const *passingConvention : { "const &", "&&" } ) // move and non-move constr 202 | { 203 | Print( s_sCurrentIndentString, s_sConstExpr, " FORCEINLINE ", className, "( " ); 204 | for( int i = 0; i < nNumFields; i++ ) 205 | { 206 | Print( "Type", i, "_t ", passingConvention, "val", i ); 207 | if ( i != nNumFields - 1 ) 208 | { 209 | Print( ", " ); 210 | } 211 | } 212 | Print( " )\n" ); 213 | PrintLn( "{" ); 214 | AdjustIndent( 1 ); 215 | for( int i = 0; i < nNumFields; i++ ) 216 | { 217 | PrintLn( "_", i, " = val", i, ";" ); 218 | } 219 | AdjustIndent( -1 ); 220 | PrintLn( "}" ); 221 | } 222 | } 223 | 224 | PrintLn(); 225 | PrintLn( "FORCEINLINE ", s_sConstExpr, "auto & operator=( std::tuple<", sTypeList, "> const &tuple )" ); 226 | PrintLn( "// Assignment from STL tuple" ); 227 | PrintLn( "{" ); 228 | AdjustIndent( 1 ); 229 | for( int i = 0; i < nNumFields; i++ ) 230 | { 231 | PrintLn( "_", i, " = std::get<", i, ">( tuple );" ); 232 | } 233 | PrintLn( "return *this;" ); 234 | AdjustIndent( -1 ); 235 | PrintLn( "}" ); 236 | 237 | // now, output member get functions. if constexpr required, 238 | for( char const *pConst : { " ", " const " } ) 239 | { 240 | PrintLn( "template" ); 241 | PrintLn( "FORCEINLINE ", s_sConstExpr, "auto", pConst, "& ", s_sMemberGetName, "()", pConst ); 242 | PrintLn( "// Member get function by field index" ); 243 | PrintLn( "{" ); 244 | AdjustIndent( 1 ); 245 | PrintLn( "static_assert( ( nFieldID >= 0 ) && ( nFieldID < ", nNumFields, " ) );" ); 246 | for( int i = 0; i < nNumFields; i++ ) 247 | { 248 | PrintLn( "if constexpr( nFieldID == ", i, ")" ); 249 | PrintLn( "{" ); 250 | AdjustIndent( 1 ); 251 | PrintLn( "return _", i, ";" ); 252 | AdjustIndent( -1 ); 253 | PrintLn( "}" ); 254 | } 255 | AdjustIndent( -1 ); 256 | PrintLn( "}\n" ); 257 | } 258 | 259 | // now, output member get functions. c++17 required for if constexpr. if not c++17, you can remove them 260 | for( char const *pConst : { " ", " const " } ) 261 | { 262 | PrintLn( "template" ); 263 | PrintLn( "FORCEINLINE ", s_sConstExpr, "auto", pConst, "& ", s_sMemberGetName, "()", pConst ); 264 | PrintLn( "// Member get function by field type" ); 265 | PrintLn( "{" ); 266 | AdjustIndent( 1 ); 267 | for( int i = 0; i < nNumFields; i++ ) 268 | { 269 | PrintLn( "if constexpr( std::is_same_v )" ); 270 | PrintLn( "{" ); 271 | AdjustIndent( 1 ); 272 | for( int j = i + 1 ; j < nNumFields; j++ ) // check for duplicate types 273 | { 274 | PrintLn( "static_assert( ! std::is_same_v, \"Duplicate types in Get\" );" ); 275 | } 276 | PrintLn( "return _", i, ";" ); 277 | AdjustIndent( -1 ); 278 | PrintLn( "}" ); 279 | } 280 | AdjustIndent( -1 ); 281 | PrintLn( "}\n" ); 282 | } 283 | 284 | 285 | PrintLn( "FORCEINLINE ", s_sConstExpr, "operator std::tuple<", sTypeList, ">() const" ); 286 | PrintLn( "// Convert to STL tuple" ); 287 | PrintLn( "{" ); 288 | AdjustIndent( 1 ); 289 | Print( s_sCurrentIndentString, "return { " ); 290 | for( int i = 0; i < nNumFields; i++ ) 291 | { 292 | int nIndex = ( bUseCompatibleLayout ) ? ( nNumFields -1 ) - i : i; 293 | Print( "_", nIndex ); 294 | if ( i != nNumFields - 1 ) 295 | { 296 | Print( ", " ); 297 | } 298 | } 299 | Print( " };\n" ); 300 | AdjustIndent( -1 ); 301 | PrintLn( "}\n" ); 302 | 303 | if ( bUseCompatibleLayout ) // can't convert to a reference if fileds aren't in the same order 304 | { 305 | PrintLn( "FORCEINLINE operator std::tuple<", sTypeList, "> &()" ); 306 | PrintLn( "// Convert to writable reference to std::tuple. **Warning**, bad idea to do this on a temporary value" ); 307 | PrintLn( "{" ); 308 | AdjustIndent( 1 ); 309 | PrintLn( "return *(( std::tuple<", sTypeList, "> * ) this );" ); 310 | AdjustIndent( -1 ); 311 | PrintLn( "}\n" ); 312 | } 313 | 314 | PrintLn( "FORCEINLINE auto operator<=>( ", className, "<", sTypeList, "> const &other ) const = default;" ); 315 | PrintLn( "// spaceship comparison operator\n" ); 316 | 317 | PrintLn( "FORCEINLINE void swap( ", className, "<", sTypeList, "> &other )" ); 318 | PrintLn( "// member swap" ); 319 | PrintLn( "{" ); 320 | AdjustIndent( 1 ); 321 | PrintLn( "std::swap( *this, other );" ); 322 | AdjustIndent( -1 ); 323 | PrintLn( "}" ); 324 | AdjustIndent( -1 ); 325 | PrintLn( "};\n" ); 326 | 327 | 328 | PrintLn( "// Deduction guide:" ); 329 | PrintLn( "template", sTemplateArgs ); 330 | Print( s_sCurrentIndentString, className, "( " ); 331 | for( int i = 0; i < nNumFields; i++ ) 332 | { 333 | Print( "Type", i, "_t arg", i ); 334 | if ( i != nNumFields -1 ) 335 | { 336 | Print( ", " ); 337 | } 338 | } 339 | Print( " ) -> ", className, "<", sTypeList, ">;\n" ); 340 | if ( s_sNameSpace.size() ) 341 | { 342 | AdjustIndent( -1 ); 343 | PrintLn( "};" ); 344 | } 345 | } 346 | 347 | void GenTuple( std::string const &className, bool bCompat ) 348 | { 349 | for( int i = 1; i <= s_nMaxArgs; i++ ) 350 | { 351 | PrintTupleDef( i, className, bCompat ); 352 | } 353 | 354 | // PrintLn( "// Deduction guide for constructing from a tuple" ); 355 | // PrintLn( "template" ); 356 | // PrintLn( " ", className, "( std::tuple const & ) -> ", className, ";" ); 357 | 358 | PrintLn( "// Override of std::get" ); 359 | PrintLn( "namespace std\n{" ); 360 | AdjustIndent( 1 ); 361 | PrintLn( "template FORCEINLINE ", s_sConstExpr, "auto const & get( ", className, " const &tuple ) { return tuple.template Get(); }" ); 362 | PrintLn( "template FORCEINLINE ", s_sConstExpr, "auto & get( ", className, " &tuple ) { return tuple.template Get(); }" ); 363 | PrintLn( "template FORCEINLINE ", s_sConstExpr, "auto get( ", className, " const &tuple ) { return tuple.template Get(); }" ); 364 | PrintLn( "template FORCEINLINE ", s_sConstExpr, "auto get( ", className, " &tuple ) { return tuple.template Get(); }" ); 365 | PrintLn( "// Deduction guide for constructing a tuple" ); 366 | PrintLn( "template" ); 367 | PrintLn( " std::tuple( ", className, " const & ) -> std::tuple;" ); 368 | 369 | 370 | AdjustIndent( -1 ); 371 | PrintLn( "}\n" ); 372 | } 373 | 374 | int main( int argc, char **argv ) 375 | { 376 | CheesyArgumentParser( argc, argv ); 377 | 378 | PrintLn( "#pragma once\n#include " ); 379 | PrintLn( "#include \n" ); 380 | PrintLn( "#ifndef FORCEINLINE" ); 381 | AdjustIndent( 1 ); 382 | PrintLn( "#ifdef __GNUC__" ); 383 | AdjustIndent( 1 ); 384 | PrintLn( "#define FORCEINLINE inline __attribute__((always_inline))" ); 385 | AdjustIndent( - 1 ); 386 | PrintLn( "#elif defined( _MSC_VER )" ); 387 | AdjustIndent( 1 ); 388 | PrintLn( "#define FORCEINLINE __forceinline" ); 389 | AdjustIndent( -1 ); 390 | PrintLn( "#endif" ); 391 | AdjustIndent( -1 ); 392 | PrintLn( "#endif" ); 393 | GenTuple( s_sTupleClassName, false ); 394 | GenTuple( s_sCompatibleTupleClassName, true ); 395 | } 396 | --------------------------------------------------------------------------------