Closed bugwelle closed 3 years ago
Hi @bugwelle,
Thank you so much for bringing this up! I pushed a fix to the main branch that addresses this issue, and added some tests to ensure that empty tuples behave properly. Please let me know if this fixes things for you!
The fix was very similar to what you suggested, although since assign
binds each value it takes to each corresponding element of the tuple, it should take no values. Also, base_list
should still be there, even though it's now empty. This is useful so that functions like apply
work without modification.
template <>
struct tuple<> : tuple_base_t<> {
constexpr static size_t N = 0;
using super = tuple_base_t<>;
using base_list = typename super::base_list;
template <other_than<tuple> U> // Preserves default assignments
requires stateless<U> // Check that U is similarly stateless
constexpr auto& operator=(U&& tup) noexcept {
return *this;
}
constexpr auto& assign() noexcept {
return *this;
}
};
Please let me know if this fixes the issue, or if there are any other changes that need to be made!
Best, Alecto
That's great! Thank you very much! :)
In case you're interested, here is a list of things I had to adapt to switch from std::tuple
to tuplet::tuple
:
std::tuple<...> abc(myvalue)
to tuplet::tuple<...> abc{myvalue}
, i.e. change the braces. No big deal, but a bit annoying š tuplet::get
into std
namespaceconst auto [i, str] = test;
), I didn't want to change all my uses of std::get<0>
especially in generic coding that takes not just tuplet
tuples but std::tuple
or std::array
as well. Missing comparison operators
Functions such as std::tie
rely on the fact that tuples can be compared. GCC implements the three-way-operator for example. To make my code compile, I added a three-way-operator for my specific use-case tuples.
To be honest, I simply took GCC's code and removed all the generic coding. For my case, I'm fine with std::strong_ordering
for example. I didn't spend a lot of time adding this, that's probably why I also had to add the equals-operator as well. Maybe you find a way to only have a generic three-way-operator.
__Missing std::forward_as_tuple()
and std::make_tuple
__
These are just convenience functions that I used. The implementation is rather simple:
Edit:// Ok, just saw your tuplet_cat and it looks promising. Using fold expression really improves the readability. :)
std::tuple_cat
:__Other notes:
I have no idea why, but by using your library, I found quite a few implicit conversion issues where I assigned unsigned integers to a tuple of signed integers, etc. No idea why the compiler didn't warn with std::tuple
, though.
So that's awesome! :)
I recommend that you use some kind of test framework. Personally, I use Catch2 for tests. You don't have a lot of tests, yet, and one file for each test is too much in my opinion. I would also add a lot of tests for tuplet::get
where the tuplet is const, non-const, where its values are const, non const, etc.
Doing so for my own library yielded many issues because I simply didn't test constness before.
Regards, Andre
Hi Andre,
I truly appreciate your response, and the effort and clarity you put into laying out improvements that could be made to the library in order to make transition as simple and painless as possible.
tuplet::tuple
to be an aggregate type, it has to forgo user-declared constructors. While this situation isn't ideal, it provides really good runtime characteristics, especially because values can be constructed in-place rather than being moved. std::get
for tuplet: I'm not sure if it's permitted to overload std::get for user-defined types. I'll do some research on this, and if it's permitted by the standard, then I'll provide an overload!One possible alternative: if std::get
is replaced with just get
, you can rely on ADL to automatically choose between std::get
for types in the standard library, and get
for user-defined types (like tuplet::tuple
). [You can see a live example here!](https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,selection:(endColumn:1,endLineNumber:10,positionColumn:1,positionLineNumber:10,selectionStartColumn:1,selectionStartLineNumber:10,startColumn:1,startLineNumber:10),source:'%23include+%3Ctuple%3E%0A%23include+%3Chttps://raw.githubusercontent.com/codeinred/tuplet/main/include/tuplet/tuple.hpp%3E%0A%23include+%3Ciostream%3E%0A%0Aauto%26+get_first(auto%26+tup)+%7B%0A++++return+get%3C0%3E(tup)%3B%0A%7D%0A%0Aint+main()+%7B%0A%0A++++std::tuple+t1+%7B+10,+20%7D%3B%0A++++tuplet::tuple+t2+%7B%22Hello%22,+%22world%22%7D%3B%0A++++std::array+arr+%7B+0.5,+0.6,+0.7%7D%3B%0A%0A++++std::cout+%3C%3C+get_first(t1)+%3C%3C+!'%5Cn!'%3B%0A++++std::cout+%3C%3C+get_first(t2)+%3C%3C+!'%5Cn!'%3B%0A++++std::cout+%3C%3C+get_first(arr)+%3C%3C+!'%5Cn!'%3B%0A%7D'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:g112,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'0',intel:'0',libraryCode:'0',trim:'1'),flagsViewOpen:'1',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,libs:!(),options:'-std%3Dc%2B%2B20',selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1,tree:'1'),l:'5',n:'0',o:'x86-64+gcc+11.2+(C%2B%2B,+Editor+%231,+Compiler+%231)',t:'0'),(h:output,i:(compiler:1,editor:1,fontScale:14,fontUsePx:'0',tree:'1',wrap:'1'),l:'5',n:'0',o:'Output+of+x86-64+gcc+11.2+(Compiler+%231)',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4)
// Because of ADL, this will work for both std::tuple and tuplet::tuple, as well as std::array and std::pair.
auto& get_first(auto& tup) {
return get<0>(tup);
}
Could you create a pull request to implement tuplet::forward_as_tuple
and tuplet::make_tuple
? If not, I'll just add them myself, but it's nice to get contributions from people!
I opted not to include implicit conversion, however if you want to convert between tuples you can do it via tuplet::convert
!
int main() {
tuplet::tuple<short, int> t1{5, 10};
// tuplet::convert handles conversion!
std::tuple<int, int> t2 = tuplet::convert(t1);
std::pair<long, float> t3 = tuplet::convert(t1);
std::array<int, 2> t4 = tuplet::convert(t1);
}
I plan on writing and publishing a longer explanation of why std::tuple
compiles slowly, however the TL;DR is that when working with all the values in a tuple, tuple::base_list
should be preferred to get
because looking up a value corresponding to a particular base class is significantly faster than overload resolution (which has to be done under the hood when using get
). This isn't a limitation of my library; it applies to the standard library too, and it's one of the reasons std::tuple
compiles slowly.
On the dev/bench-compile branch of this project, I have a script benchmarking the compile time of apply
for both std::tuple
and tuplet::tuple
, and tuplet
achieves a speedup of dozens to hundreds of times for large numbers of elements. (It's faster in all cases, including for small numbers, but large numbers are where it really shines.)
You can benchmark it on your own machine by running:
git clone https://github.com/codeinred/tuplet.git
cd tuplet
git checkout dev/bench-compile
cd bench-compile-times
./bench-apply.sh 10 450
The output should look something like this. the number in the first column is the number of elements in the tuple, the number in the second column is the time to compile, and the number in the third column is the memory usage during compilation. The compile time of apply
on std::tuple
grows at about O(n^2)
relative to the number of elements, whereas for tuplet its
O(n log(n))`. There's a minimum time of about 0.08s in the below data, but that's just the time it takes the compiler to load the header files.
Using g++ aka g++ (GCC) 11.2.0
tuplet::tuple: 10, 0.08, 31872
std::tuple: 10, 0.15, 46256
tuplet::tuple: 20, 0.08, 32468
std::tuple: 20, 0.18, 55144
tuplet::tuple: 30, 0.08, 32884
std::tuple: 30, 0.22, 67344
tuplet::tuple: 40, 0.08, 33324
std::tuple: 40, 0.28, 83940
tuplet::tuple: 50, 0.09, 34196
std::tuple: 50, 0.33, 103764
tuplet::tuple: 60, 0.09, 34408
std::tuple: 60, 0.41, 125948
tuplet::tuple: 70, 0.09, 35236
std::tuple: 70, 0.49, 162056
tuplet::tuple: 80, 0.09, 35716
std::tuple: 80, 0.60, 201336
tuplet::tuple: 90, 0.09, 36248
std::tuple: 90, 0.72, 243452
tuplet::tuple: 100, 0.09, 36752
std::tuple: 100, 0.87, 289756
tuplet::tuple: 110, 0.10, 36980
std::tuple: 110, 1.08, 334568
tuplet::tuple: 120, 0.10, 37660
std::tuple: 120, 1.30, 388500
tuplet::tuple: 130, 0.10, 38444
std::tuple: 130, 1.52, 467996
Dear Alecto,
thank you very much for your response and all of your effort. I very much appreciate it! :)
- Regarding the lack of a constructor, this was an intentional choice. In order for
tuplet::tuple
to be an aggregate type, it has to forgo user-declared constructors. While this situation isn't ideal, it provides really good runtime characteristics, especially because values can be constructed in-place rather than being moved.
Could you expand on that a little bit or maybe point me to some resources in that regard? How are the runtime characteristics better? I'm interested in that topic as it may be useful for my own projects.
- Regarding
std::get
for tuplet: I'm not sure if it's permitted to overload std::get for user-defined types. I'll do some research on this, and if it's permitted by the standard, then I'll provide an overload!
Raymond Chen does it here. That's what I read when I wanted to have structured bindings for my own types. If you find that it's not allowed by the standard, please let me know. :)
One possible alternative: if
std::get
is replaced with justget
, you can rely on ADL to automatically choose betweenstd::get
for types in the standard library, andget
for user-defined types (liketuplet::tuple
).
ADL is (sometimes) still magic to me and I had at least one bug in the past due to ADL which is why I try to avoid it. :)
Could you create a pull request to implement
tuplet::forward_as_tuple
andtuplet::make_tuple
? If not, I'll just add them myself, but it's nice to get contributions from people!
Sure! :)
I plan on writing and publishing a longer explanation of why
std::tuple
That would be great! :)
I opted not to include implicit conversion
Which is great IMHO. Thank you for clarifying this. May I hijack this thread to ask you how this detail in your tuple_cat works?
std::forward<Tup>(t).identity_t<Bases>::value
Where does indentity_t
come from? I see it's implementation at the beginning of tuple.hpp
but I don't see it used anywhere except in usage such as above. Is identity_t
a member variable in some tuple?
I just compared compile times of my projects for std::tuple
vs tuplet::tuple
.
std::tuple
, std::tuple_cat
, etc.CMakePresets.json
:
{
"version": 2,
"cmakeMinimumRequired": {
"major": 3,
"minor": 20,
"patch": 0
},
"configurePresets": [
{
"name": "test",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/test",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_CXX_FLAGS": "-march=native",
"CMAKE_CXX_COMPILER": "g++",
"FUSION_BUILD_TEST": "ON",
"FUSION_BUILD_EXAMPLES": "ON"
}
}
],
"buildPresets": [
{
"name": "test",
"configurePreset": "test"
}
]
}
Compile Commands:
rm -rf build/test
cmake --preset test
time cmake --build --preset test
Note that I'm using ninja
which uses all of my 24 cores.
# With Tuplet
cmake --build --preset test 379,97s user 17,20s system 2075% cpu 19,132 total
cmake --build --preset test 380,02s user 16,82s system 2073% cpu 19,138 total
cmake --build --preset test 381,08s user 16,74s system 2033% cpu 19,559 total
cmake --build --preset test 383,23s user 16,99s system 2072% cpu 19,307 total
cmake --build --preset test 383,51s user 17,16s system 2098% cpu 19,092 total
# No Tuplet
cmake --build --preset test 409,68s user 17,09s system 2169% cpu 19,673 total
cmake --build --preset test 409,22s user 17,13s system 2125% cpu 20,063 total
cmake --build --preset test 411,33s user 16,98s system 2106% cpu 20,328 total
cmake --build --preset test 416,20s user 17,53s system 2096% cpu 20,692 total
cmake --build --preset test 412,12s user 17,41s system 2158% cpu 19,897 total
Time spent in user space is 30 seconds less. However, to the user, i.e. to me, there is a difference of maybe about a second.
If I pass -j 1
, i.e. only use one thread, the times change a bit:
# With Tuplet
cmake --build --preset test -j 1 222,30s user 10,58s system 99% cpu 3:53,10 total
cmake --build --preset test -j 1 223,55s user 10,56s system 99% cpu 3:54,32 total
# No Tuplet
cmake --build --preset test -j 1 237,27s user 10,08s system 99% cpu 4:07,59 total
cmake --build --preset test -j 1 241,14s user 10,84s system 99% cpu 4:12,25 total
So in single threaded cases, the compile time went down by more than 10 seconds. :)
Dear Andre,
I'm happy to announce that tuplet
now supports comparisons with operator <=>
and operator ==
, from which the other comparison and equality providers can be obtained automatically.
Thankfully, we were able to default these operators for tuple
and it's base type, type_map
:
template <class... Bases>
struct type_map : Bases... {
// ...
auto operator<=>(type_map const&) const = default;
bool operator==(type_map const&) const = default;
};
template <class... T>
struct tuple : tuple_base_t<T...> {
// ...
auto operator<=>(tuple const&) const = default;
bool operator==(tuple const&) const = default;
};
tuple
types are supposed to provide comparison operators even if they contain references (such as in the case of tuple<int&, int&>
, and unfortunately defaulting isn't allowed in these cases. My initial implementation to provide comparison in the case of references was to split tuple_elem
into two classes:
However, this didn't handle r-value references, it wasn't clear if it handled const references, and it was rather verbose. Thankfully, I discovered that we could provide an alternative by taking advantage of requires
:
This worked on MSVC, GCC, and Clang with libstdc++, but it broke when using libc++ (it was weird). It was breaking in tests where comparisons weren't even being used. Thankfully, this could be fixed by further constraining <=>
and ==
, so that they explicitly required types to provide the appropriate operations:
Ultimately, I'm happy with the resulting code, and it looks sensible (despite the need for workarounds). You can see the details of this implementation and it's changes in #8.
std::get
overloadingI looked over Raymond Chen's article, and as far as I can tell, he doesn't overload std::get
. He provides a get
function, but he does it at the same namespace scope as the Person
class (which appears to be the global scope). I posted a thread asking about it on r/cpp. There wasn't a conclusive answer, however the general consensus leaned in the direction of discouraging it. Given the ambiguity, I'd prefer not to include it in the library, however I have two ideas!
namespace std { using tuplet::get; }
std::get
and tuplet::get
in your namespace, and then you won't have to rely on ADL:
namespace bugwelle {
using tuplet::get;
using std::get;
}
This second solution is the one I recommend, and it follows the principle of least surprise (it's definitely standards compliant, and it's overall a pretty sensible thing to do)
identity_t
implementation detailtuplet contains a lot of code similar to this:
std::forward<Tup>(t).identity_t<Bases>::value
The general case of this syntax is value.BaseClass::member
. Each of a tuple's base classes has a value member, so given a list of base classes, we can quickly access all values in the tuple:
template <class F, class Tup, class... Bases>
auto apply_impl(F&& f, Tup&& tup, type_list<Bases...> unused) {
return f(tup.Bases::value ...); // Get all the values of the tuple
}
[This works in GCC](https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,selection:(endColumn:27,endLineNumber:7,positionColumn:27,positionLineNumber:7,selectionStartColumn:27,selectionStartLineNumber:7,startColumn:27,startLineNumber:7),source:'%23include+%3Chttps://raw.githubusercontent.com/codeinred/tuplet/main/include/tuplet/tuple.hpp%3E%0A%23include+%3Ccstdio%3E%0Ausing+namespace+tuplet%3B%0A%0Atemplate+%3Cclass+F,+class+Tup,+class...+Bases%3E%0Aauto+apply_impl(F%26%26+f,+Tup%26%26+tup,+type_list%3CBases...%3E+unused)+%7B%0A++++return+f(tup.Bases::value+...)%3B+//+Get+all+the+values+of+the+tuple%0A%7D%0A%0Aint+main()+%7B%0A++++using+tup+%3D+tuple%3Cint,+int,+int%3E%3B%0A++++using+base_list+%3D+typename+tup::base_list%3B%0A++++auto+add+%3D+%5B%5D(auto...+values)+%7B+return+(values+%2B+...)%3B+%7D%3B%0A++++int+sum+%3D+apply_impl(add,+tup%7B1,2,3%7D,+base_list%7B%7D)%3B%0A%0A++++if+(sum+%3D%3D+6)+%7B%0A++++++++puts(%22Good!!%22)%3B%0A++++%7D+else+%7B%0A++++++++puts(%22Bad%22)%3B%0A++++%7D%0A%7D'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:g112,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'0',intel:'0',libraryCode:'0',trim:'1'),flagsViewOpen:'1',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,libs:!(),options:'-std%3Dc%2B%2B20',selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1,tree:'1'),l:'5',n:'0',o:'x86-64+gcc+11.2+(C%2B%2B,+Editor+%231,+Compiler+%231)',t:'0'),(h:output,i:(compiler:1,editor:1,fontScale:14,fontUsePx:'0',tree:'1',wrap:'1'),l:'5',n:'0',o:'Output+of+x86-64+gcc+11.2+(Compiler+%231)',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4), however [it fails to compile in clang.](https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,selection:(endColumn:34,endLineNumber:12,positionColumn:34,positionLineNumber:12,selectionStartColumn:34,selectionStartLineNumber:12,startColumn:34,startLineNumber:12),source:'%23include+%3Chttps://raw.githubusercontent.com/codeinred/tuplet/main/include/tuplet/tuple.hpp%3E%0A%23include+%3Ccstdio%3E%0Ausing+namespace+tuplet%3B%0A%0Atemplate+%3Cclass+F,+class+Tup,+class...+Bases%3E%0Aauto+apply_impl(F%26%26+f,+Tup%26%26+tup,+type_list%3CBases...%3E+unused)+%7B%0A++++return+f(tup.Bases::value+...)%3B+//+Get+all+the+values+of+the+tuple%0A%7D%0A%0Aint+main()+%7B%0A++++using+tup+%3D+tuple%3Cint,+int,+int%3E%3B%0A++++using+base_list+%3D+typename+tup::base_list%3B%0A++++auto+add+%3D+%5B%5D(auto...+values)+%7B+return+(values+%2B+...)%3B+%7D%3B%0A++++int+sum+%3D+apply_impl(add,+tup%7B1,2,3%7D,+base_list%7B%7D)%3B%0A%0A++++if+(sum+%3D%3D+6)+%7B%0A++++++++puts(%22Good!!%22)%3B%0A++++%7D+else+%7B%0A++++++++puts(%22Bad%22)%3B%0A++++%7D%0A%7D'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:clang1101,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'0',intel:'0',libraryCode:'0',trim:'1'),flagsViewOpen:'1',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,libs:!(),options:'-std%3Dc%2B%2B20',selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1,tree:'1'),l:'5',n:'0',o:'x86-64+clang+11.0.1+(C%2B%2B,+Editor+%231,+Compiler+%231)',t:'0'),(h:output,i:(compiler:1,editor:1,fontScale:14,fontUsePx:'0',tree:'1',wrap:'1'),l:'5',n:'0',o:'Output+of+x86-64+clang+11.0.1+(Compiler+%231)',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4)
The use of identity_t
is a workaround that forces clang to treat Bases
as a type parameter pack, allowing it to be expanded and used with this context. Because identity_t<T>
just produces T
, this is semantically equivalent to the above usage of tup.Bases::value
, and so it works without issue in GCC and MSVC (which happens to suffer from the same issue as clang).
template <class F, class Tup, class... Bases>
auto apply_impl(F&& f, Tup&& tup, type_list<Bases...> unused) {
return f(tup.identity_t<Bases>::value ...); // Get all the values of the tuple
}
In summary: identity_t
is not a member of tuple
. identity_t<Bases>
just aliases Bases
, but by using Bases
in this context, clang and MSVC treat it as a parameter pack and allow it to be expanded.
Despite the identity_t
workaround, looking up values this way ends up being much faster at compile time for looking up values in a tuple than either get<I>
or operator[]
. This is because both get<I>
and operator[]
result in overload resolution, which is more complex operation for the compiler, especially with large numbers of overloads.
For more information, see here
Provided an aggregate doesn't contain any references, it'll be compatible with a C memory layout. It'll also be memcpy
able when the members of the tuple are memcpy
able, and you get the copy and move constructors basically for free. You also get copy assignment and move assignment for free. I don't enjoy writing constructors with a bunch of overloads, and the constructor for tuple types is particularly nasty when you don't use an aggregate, because you want to be able to construct types from any elements that can be used to construct them. There's the added benefit that the compiler is very good at producing efficient code for aggregates, as seen here!
Aggregates also compose very nicely - if you have a tuplet::tuple
containing two std::array
s you can just pass all the elements together:
tuplet::tuple<std::array<int, 3>, std::array<int, 4>> tup { 1, 2, 3, 4, 5, 6, 7 };
This particular example isn't that useful, but I use the same property in another library I'm working on that relies on tuplet::tuple
being an aggregate to simplify the code and to avoid having to write a forwarding constructor.
Also! Aggregates can store types that can't be moved! This isn't super widely applicable, but I think it's neat! See example here. If you swapped tuplet::tuple
with std::tuple
it wouldn't compile.
We have make_tuple
, forward_as_tuple
, tuple_cat
, comparison operators, and I think I've addressed the other suggested changes, so I'm going to close this issue! Thank you so much for your help with the project. I really appreciate it! If there's any other issues you encounter, or if you have any suggestions or improvements, please reach out. It was nice working with you!
I still plan on integrating the Catch2 Testing Library, and I'd definitely appreciate your help there! (You can open another issue for that one if you'd like!)
Best, Alecto
Hi,
Writing from mobile, let's see if autocorrect works š
I'm happy to announce that
tuplet
now supports comparisons withoperator <=>
andoperator ==
, from which the other comparison and equality providers can be obtained automatically.
Great! Thank you very much!
I looked over Raymond Chen's article, and as far as I can tell, he doesn't overload
std::get
. He provides aget
function, but he does it at the same namespace scope as thePerson
class (which appears to be the global scope).
Oh God, I'm sorry. Maybe it was some other blog where I saw that. But never mind. I saw the reddit thread and saw that apparently ADL has changed a bit in C++20 of which I wasn't aware. I still dislike writing get without std for some reason, but I'll get used to it. :)
Your second example looks great. I already have everything in my own name space, so that's a great solution. Thank you very much!
The general case of this syntax is
value.BaseClass::member
. Each of a tuple's base classes has a value member, so given a list of base classes, we can quickly access all values in the tuple:
Cool! I didn't know that works š
In summary:
identity_t
is not a member oftuple
.identity_t<Bases>
just aliasesBases
, but by usingBases
in this context, clang and MSVC treat it as a parameter pack and allow it to be expanded.
Just wow. I have no idea how the compiler knows that indentity_t is not a member but rather a type but that's something I'll look into another time. I'm just amazed that that works.
Aggregates also compose very nicely - if you have a
tuplet::tuple
containing twostd::array
s you can just pass all the elements together:tuplet::tuple<std::array<int, 3>, std::array<int, 4>> tup { 1, 2, 3, 4, 5, 6, 7 };
That... Is exactly what I need. I'm not joking, this is perfect š
Iām really glad that tuple being an aggregate provided what you needed!
btw!!! tuple_cat is much more efficient now! We found a non-recursive implementation and then figured out how to ensure optimal assembly for both gcc and clang! See #14, #10 and #11!
I saw those commits and am already using the latest version. :-)
I like the concept of the tuple of tuples that is then flattened. I used something similar in another library, but with recursive templates. Your implementation is awesome. š
My use case is a multidimensional range, where each dimension is stored as an aggregate type, e.g.
// simplified
struct Dim {
int start{};
int end{};
};
and if I have N dimensions, I have a tuple of N times Dim
.
Instead of:
range({0,Z}, {0,Y}, {0,X}); // simplified
I can now write
range({0,Z, 0,Y, 0,X}); // simplified
It may not seem like much, but having an aggregate type, I was able to remove a lot of boilerplate code and template specializations that I had.
I'm currently writing my master's thesis. This "range type" is only a small part of my thesis, but I'm still very happy to have found this. I'd like to cite your project. Do you have any preferences as to how you would like to be cited? :)
I'd go with this bibtex entry:
@software{tuplet_2021,
author = { Perez, Alecto Irene },
title = {{tuplet: A Lightweight Tuple Library for Modern C++}},
url = {https://github.com/codeinred/tuplet},
version = {1.1.1},
year = {2021},
month = {10},
}
That would be amazing, thank you so much! I don't have any strong preferences with regard to being cited, and the citation you provided looks good! If you need a git tag I do my best to provide one for each release / updated version!
I think version 1.1.1 is enough information in this case. Thank you for all your work. :)
Hi,
first of all: Great library! I've learned a few C++ tricks here and there while reading the code.
I wanted to try this library as a replacement for
std::tuple
in my code base to check if it improves (compile) performance. Unfortunately, there are a few minor to major differences tostd::tuple
that make it difficult to use it as a drop-in replacement.One case I heavily rely on in my code base is the use of empty tuples.
Example:
See https://godbolt.org/z/ojYM79q3K
Motivation
I've written some generic code that stores lambdas in tuples. And I've got some code that can append other lambdas to this tuple (and a new tuple is returned). Because it's quite generic, it may happen that one tuple is empty.
How to fix
A quick&dirty fix I've used is to add something like this: