Tuplet
A fast, simple tuple implementation that implements tuple as an aggregate
Install / Use
/learn @codeinred/TupletREADME
tuplet: A Lightweight Tuple Library for Modern C++
tuplet is a one-header library that implements a fast and lightweight tuple
type, tuplet::tuple, that guarantees performance, fast compile times, and a
sensible and efficent data layout. A tuplet::tuple is implemented as an
aggregate containing it's elements, and this ensures that it's
- trivially copyable,
- trivially moveable,
- trivially assignable,
- trivially constructible,
- and trivially destructible.
This results in better code generation by the compiler, allowing tuplet::tuple
to be passed in registers, and to be serialized and deserialized via memcpy.
If you'd like a further discussion of how tuplet::tuple compares to
std::tuple and why you should use it, see the Motivation
section below!
Usage
Creating a tuple is as simple as 1, 2, "Hello, world"! Writing
tuplet::tuple tup = {1, 2, std::string("Hello, world!")};
Will create a tuple of type tuple<int, int, std::string>, just like you'd
expect it to. This is all you need to get started, but the following sections
will expand upon the functionality provided by tuplet in greater depth.
Access members with get() or the index operator
You can access members via get:
std::cout << get<2>(tup) << std::endl; // Prints "Hello, world!"
Or via operator[]:
using tuplet::tag;
std::cout << tup[tag<2>()] << std::endl; // Prints "Hello, world!"
Something that's important to note is that tag is just an alias for
std::integral_constant:
template <size_t I>
using tag = std::integral_constant<size_t, I>;
Use Index Literals for Clean, Easy Access
You can access elements of a tuple very cleanly by using the _tag literal
provided in tuplet::literals! This namespace defines the literal operator
_tag, which take number and produce a tuplet::tag templated on that number,
so 0_tag evaluates to tuplet::tag<0>(), 1_tag evaluates to
tuplet::tag<1>(), and so on!
using namespace tuplet::literals;
tuplet::tuple tup = {1, 2, std::string("Hello, world!")};
std::cout << tup[0_tag] << std::endl; // Prints 1
std::cout << tup[1_tag] << std::endl; // Prints 2
std::cout << tup[2_tag] << std::endl; // Prints Hello World
Decompose tuples via Structured Bindings
The tuple can also be accessed via a structured binding:
// a binds to get<0>(tup),
// b binds to get<1>(tup), and
// c binds to get<2>(tup)
auto& [a, b, c] = tup;
std::cout << c << std::endl; // Print "Hello, world!"
Tie values together with tuplet::tie()
You can create a tuple of references with tuplet::tie! This function acts just
like std::tie does:
int a;
int b;
std::string s;
// Creates a tuplet::tuple<int&, int&, std::string&>
tuplet::tuple tup = tuplet::tie(a, b, s);
// a will be set to 1,
// b will be set to 2, and
// s will be set to "Hello, world!"
tup = tuplet::tuple{1, 2, "Hello, world!"};
std::cout << s << std::endl; // Prints Hello World
Assign Values via tuple.assign()
It's possible to easily and efficently assign values to a tuple using the
.assign() method:
tuplet::tuple<int, int, std::string> tup;
tup.assign(1, 2, "Hello, world!");
Store references using std::ref()
You can use std::ref to store references inside a tuple!
std::string message;
// t has type tuple<int, int, std::string&>
tuplet::tuple t = {1, 2, std::ref(message)};
message = "Hello, world!";
std::cout << get<2>(t) << std::endl; // Prints Hello, world!
You can also store a reference by specifying it as part of the type of the tuple:
// Stores a reference to message
tuplet::tuple<int, int, std::string&> t = {1, 2, message};
These methods are equivilant, but the one with std::ref can result in cleaner
and shorter code, so the template deduction guide accounts for it.
Use elements as function args with tuplet::apply()
As with std::apply, you can use tuplet::apply to use the elements of a tuple
as arguments of a function, like so:
// Prints arguments on successive lines
auto print = [](auto&... args) {
((std::cout << args << '\n') , ...);
};
apply(print, tuplet::tuple{1, 2, "Hello, world!"});
Additional Features
tuplet has been backported to C++17. Functions that were constrained with
requires clauses will still be constrained in C++20, with sfinae being used
where necessary if concepts are not availible.
Tuplet remains trivially copyable and trivially movable, with no user-provided copy or move constructors.
tuplet::tuple provides the following operations on the elements of a tuple:
tuple.any(func)- returns true if the function returns true for any of the tuple's elements.tuplet.all(func)- returns true if the function returns true for all of the tuple's elementstuplet.map(func)- returns a new tuple, whose elements consist of the values returned by the function when it's applied to each element of the tuple separatelytuplet.for_each- applies a function to each element in a tuple, discarding the value
These are bulk operations, and they'll compile significantly faster than lookup
with std::get for large tuples.
Additionally, tuplet now supports heterogenous comparisons - you can compare a
tuple<int> with tuple<int&>, or with tuple<long>. This can be useful when
writing test code.
Explicit arbitrary conversion with tuplet::convert
You can use tuplet::convert to convert a tuplet to other arbitrary compatible
types:
struct my_struct {
int a;
double b;
std::string_view c;
};
auto tup = tuplet::tuple { 1, 0.3, "Hello world" };
my_struct s = tuplet::convert { tup };
Any type that can be constructed with braced-initialization from the elements of a tuple is considered compatible. For tuples of appropriate types, this includes vectors, arrays, structs, and other class types.
If the tuple is moved into tuplet::convert, then any values in the tuple will
be moved into the created object.
Installation
CMake package
Tuplet can now be installed as a CMake package!
git clone https://github.com/codeinred/tuplet.git
cd tuplet
cmake -B build -DCMAKE_INSTALL_PREFIX="/path/to/install"
cmake --build build
cmake --build build --target install
If you're installing tuplet globally, you may have to run the final command with sudo:
# Global install
git clone https://github.com/codeinred/tuplet.git
cd tuplet
cmake -B build
cmake --build build
sudo cmake --build build --target install
This will attempt to build tests. If the default system compiler doesn't support
C++20 and buliding fails, you can use an alternative compiler by specifying
-DCMAKE_CXX_COMPILER during the configuration step:
cmake -B build -DCMAKE_CXX_COMPILER=g++-11
Alternatively, on newer versions of CMake (e.g, cmake 3.15 and above), you can skip the build step entirely. See this documentation for more information.
git clone https://github.com/codeinred/tuplet.git
cd tuplet
cmake -B build
sudo cmake --install build
# Or:
cmake --install build --prefix "/path/to/install"
Once tuplet is installed, it can now be discovered via find_package, and
targeted via target_link_libraries. It's a header-only library, but this will
ensure that tuplet's directory is added to the include path.
cmake_minimum_required(VERSION 3.14)
project(my_project LANGUAGES CXX)
find_package(tuplet REQUIRED)
add_executable(main)
target_sources(main PRIVATE main.cpp)
target_link_libraries(main PRIVATE tuplet::tuplet)
Conan package
You can install tuplet using the Conan package manager.
Add tuplet/1.2.2 to your conanfile.txt's require clause. This way you can
integrate tuplet with any
build system Conan
supports.
Motivation
This section intends to address a single fundamental question: Why would I use
this instead of std::tuple?
It is my hope that by addressing this question, I might explain my purpose for writing this library, as well as providing a clearer overview of what it provides.
std::tuple is not a zero-cost abstraction, and using it introduces a runtime
penalty in comparison to traditional aggregate datatypes, such as structs.
std::tuple also compiles slowly, introducing a penalty on libraries that make
extensive use of it.
tuplet::tuple has none of these problems.
-
tuplet::tuplean aggregate type.- When the elements are trivially constructible,
tuplet::tupleis trivially constructible - When elements are trivially destructible,
tuplet::tupleis trivially destructible
- When the elements are trivially constructible,
-
tuplet::tuplecan be passed in the registers. This means that there's's no overhead compared to a struct -
Compilation is much faster, especially for larger or more complex tuples.
This occurs because
tuplet::tupleis an aggregate type, and also because indexing was specifically designed in a way that allowed for faster lookp of elements. -
tuplet::tupletakes advantage of empty-base-optimization and[[no_unique_address]]. This means that empty types don't contribute to the size of the tuple.
Can std::tuple be rewritten to have these properties?
Not without both an ABI break and a change to it's API. There are a few reasons for this.
- The memory layout of
std::tupletends to be in reverse order when compared to a corresponding struct containing the same types. Fixing this would be an ABI break. - Because
std::tupleisn't trivially copyable and isn't an aggregate, it tends to be passed on the stack instead of in the registers. Fixing this would be an ABI break. - [The constructor of
std::tupleprovides overloads for passing an allocator to the constructor](https://en.cppreference.com/w/cpp/utility/tup
Related Skills
node-connect
344.1kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
96.8kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
344.1kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
344.1kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
