Copyright © 2024 The Lemuriad. Distributed under BSL V1.0
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.| GHA meson build: linux gcc 12, clang 14, MSVC latest |
Please experiment with tupl and report any issues |
Click to expand
tupl is a C++20 library named after its core type:
tupl: a Rule of Zero tuple type that wants to be a builtin.
It depends only on std library <concepts> and <compare>.
It follows familiar tuple APIs, as far as possible for an aggregate
type, and also introduces some API innovations.
Imagine if C++ had tuples built in to the language...
-
struct<Xs...>$\rightarrow$ struct { Xs...xs; };
That is, a tuple is a simple struct
aggregate
with types Xs...
expanded directly as data members in the struct body.
For example, a pair would be:
-
struct<X0,X1>$\rightarrow$ struct {X0 x0; X1 x1;};
The tupl and lupl library types approach this builtin ideal.
struct {[[no_unique_address]] Xs...xs;};
With
[[no_unique_address]]
attribute on all its members
the compiler is allowed to optimize tupl member layout.
Now, tupl layout has no portability guarantees so we also
need a stable,
standard-layout
type; a 'layout tupl':
-
lupl<Xs...>$\rightarrow$ struct { Xs...xs; };
Use lupl for guaranteed, portable standard layout.
Use tupl if layout optimization is more important.
From now on we take tupl to mean 'tupl or lupl'.
- If
Xs...are all trivial / structural / regular types
thentupl<Xs...>is a trivial / structural / regular type.
Aggregate structs acquire shared properties of their members.
In particular, a tupl is
TriviallyCopyable
if all its elements are.
Trivial types are cheap to pass to and return from functions, and
triviality enables deeper inlining and optimization of call stacks.
tupl has no non-static member functions.
A
Rule of Zero
type,
it relies on builtin language mechanics for much of its 'API'.
tupl is used primarily as a value type (its CTAD deduces values).
tupl also serves as a base class for deriving other tuplish types
that may hold references, add constructors or member functions.
tupl has two traits that identify it as a
tuplish type.
- A member typedef
tupl_t = tupl - A function
map(t,f) -> f(t.xs...)
map is a hidden friend function of tupl.
map(t,f) calls f with the members xs... of tupl_t t.
Types derived from tupl inherit these tuplish traits.
The library defines three derived tuplish types:
-
tiesis a reference-tupl with assign-through semantics.
It supports non-same-type 'heterogeneous' assignments. -
valsis a value tupl with extended assignment operators. -
cmpsis a tupl with heterogeneous comparison operators.
ties, vals and cmps, like tupl, are all aggregate types.
std::tuple operations are heterogeneous.
- It constructs from any list of convertible-from types.
- It assigns from any tuple of assignable-from types.
- It compares with any tuple of comparable-with types.
In retrospect, these defaults might not be chosen today.
tupl's builtin operators and default comparisons naturally work
only between operands of the same type.
This same-type restriction is a safe default for a core tuple type.
For mixed type, heterogeneous operations use the appropriate
specialized tuplish type or one of the free functions provided.
tupl mostly just works as you'd expect a C++ tuple type to work.
The examples below highlight cases where tupl differs from tuple.
The differences derive from tupl's aggregate nature. Understanding
aggregates is key to comprehending and making best use of tupl.
As an aggregate type tupl has no constructors so initialization means
aggregate initialization; it's builtin.
tupl embraces curly braces as the natural way
to initialize aggregates.
tupl supports class template argument deduction
{CTAD},
with braces:
tupl<char[4],int> cc{"C++",14}; // Explicit type
tupl cppstd{"c++",17}; // CTAD -> tupl<char[4],int>Note how the string literal "c++" is deduced as char[4], and initialized.
Different size string literals result in different types.
C arrays are also aggregates but they're not regular, copyable types.
The tupl library treats them as if they're regular, but reality bytes;
C arrays remain an awkward edge case, so feature in most examples.
String literals are overused in the snippets as convenient C array literals
that also conveniently succeed in initializing their char[] array target.
std::tuple would deduce char const* and force decay-to-pointer.
With tupl, the programmer must take the address explicitly, if wanted:
tupl cpp{&"cpp"}; // CTAD -> tupl<const char*>tupl assignment is also builtin as it defines no operator= overloads.
We call assignment from braced initializer lists 'aggregate assignment'
because it works by aggregate initialization followed by copy-assignment.
A right hand side temporary of left hand side type is aggregate initialized
then copy-assigned to the left hand side (hopefully with copies elided):
cppstd = {"c++",17}; // 'Aggregate assignment'
cppstd = {{'c','+','+','\0'},17}; // Equivalent
cppstd = {}; // Aggregate assign clear all fields
cppstd = {"cpp"}; // Warning: missing initializer
cppstd = {"cplusplus",20}; // FAILs to compile
// char[10] ^^^^^^^^^ too large for char[4] fieldBraces catch narrowing conversions in initialization and assignment.
There's a warning when unsigned next is assigned to the int field:
const char lang[] = "cpp"; // array variable
unsigned int next = {26U}; // unsigned vs signed
cppstd = {"cpp",next}; // Warning/error: narrowing
cppstd = {lang,26}; // FAILs to compile:
// array ^^^^ initializer can't initialize arrayHere, the array variable lang can't initialize the array field
(string literals are the only array values that are valid initializers)
(workarounds to deal with arrays are presented below).
Comparisons also work by builtin aggregate rules, with library opt-in:
if (cppstd != tupl<char[4],int>{})
return cppstd < tupl{"c++",20};Unfortunately, the grammar doesn't allow a braced initializer list as the
right hand side of a comparison operator, so an explicit tupl type has to
be constructed in order to use the comparison operators.
The following material is specific to C arrays and can be skipped.
tupl goes out of its way to support C arrays as-if regular values.
C arrays are aggregates and aggregates should stick together.
Details and rationale of C array support.
C arrays are irregular objects; they're not copyable or comparable.
C arrays, when members of structs, are regularized by language rules
that make structs copyable; copy-constructors and copy-assignment
operators are implicitly synthesized to deal with array members.
Comparisons can also be synthesized automatically, since C++20.
If operator<=> or operator== are user-defined as =default
then lexicographic comparisons are generated for array members.
tupl naturally regularizes C arrays using this language support.
tupl also goes further:
tuplassumes that C arrays are regular copyable objects.
It treats arrays as regular and doesn't accommodate their irregularity.
Attempts to copy arrays work if possible, or cause compiler errors,
and never result in silent decay-to-pointer and pointer copy.
tupl is designed to be ready for a future in which C arrays are regular
with workarounds to deal with the reality that they aren't regular yet.
The CTAD guide is a consequence of this design choice:
tupldeduces arrays as arrays, with no decays.
Now, array members deduced from array-valued initializers cannot be
initialized by those initializers (!) because language rules don't provide
copy construction of array members from array variable initializers.
tupl custom CTAD guide, deduces values with no decay.
template <typename...Xs> tupl(Xs const&...)
-> tupl<Xs...>;tupl, as a struct aggregate, has an implicit default deduction guide
that acts to decay array-valued initializers (apart from string literals)
to pointers.
This deduction guide overrides it to prevent the decay.
tupl deduces arrays from array initializers, but initialization fails.
The workaround is to use a function to do the initialization:
tupl_init'maker function' is a workaround for arrays.
char a[]{"A"};
tupl c{a}; // FAILS: can't initialize tupl<char[2]>
#include <tupl/tupl_cat.hpp>
auto c = tupl_init(a); // Maker function workaroundLet's take a slightly higher level look at tupl's API.
#include <tupl/tupl.hpp>
constexpr lml::tupl t012 = {0,1U,"2"};
auto tupl_API(lml::tupl<int,unsigned,char[2]> t = t012)
{
/*
... Example code expanded below ...
*/
return lml::tupl{t, true}; // Nested tupl return
}
auto [ret,err] = tupl_API(); // Structured bindingtupl_API takes argument t of type tupl<int,unsigned,char[2]>
the same type deduced for global constant t012 from {0,1U,"2"}.
The return type is tupl<tupl<int,unsigned,char[2]>, bool>
Note: Compilers disagree on the layout and
sizeofthe return type.
For portable standard layout use 'layout tuple'luplinstead oftupl.
Otherwise,tuplmay be more efficient. See layout.
This tupl type is a regular type, both copyable and comparable, because
all its fields are regular, or arrays of regular, types.
Unlike std::tuple, only same tupl types are copyable and comparable:
tupl<int,int,char[2]> d{t}; // FAIL: type mismatch
auto s{t}; // Copy construct, as struct aggregate
assert(s == t); // Builtin compare for same types
s = t; // Copy-assign, aggregate struct builtin
s = {0,1,"2"}; // OK, 'aggregate assignment'
s = tupl{0,1,"2"}; // FAILs to compile: mismatch
assert(s == tupl{0,1,"2"}); // FAIL: type mismatch
assert(equals(s,{0,1,"2"})); // OK, converts typesFree functions equals and compare3way can compare with braced list
arguments, including the empty list (but incomplete lists are rejected):
equals(t,{});
compare3way(t,{1,2,"3"});tupl has a specialized swap free function (not a friend and not a CPO)
(it is otherwise specified similarly to C++20
std::ranges::swap).
You should use the
ADL 'two step'
if std types may be involved:
using std::swap;
swap(s,t); // Specialized elementwise lml::swapStructured bindings to tupl use the builtin aggregate rule (and not the
std 'tuple protocol' which involves specializing std template traits):
auto [ret,err] = tupl_API();A plain auto binding makes a copy of the right hand side
(a copy is generally wanted when binding to a function return type).
For mutable access, though, remember to use a reference binding;
&& is always good:
auto&& [i,u,c2] = s;Structured binding looks a bit like the inverse of aggregate initialization.
These bindings let us illustrate pros and cons of aggregate initialization:
t = {i,u}; // WARN of missing initializer, and...
t = {u,i}; // WARN or FAIL, narrowing conversions
t = {i,u,c2}; // FAIL: array variable initializer
tupl c{i,u,c2}; // FAIL: array variable initializerCompilers will warn about missing initializers or implicit conversions.
Make sure that the warnings are enabled, and act on them, then struct
aggregates are as safe as or safer than tuples that have constructors.
We've seen that aggregate initialization doesn't accept
C array variables
as initializers so this also hits aggregate assignment.
The workaround for dealing with array variables is to use free functions
assign or assign_elements (they're also defined for C arrays):
assign(t) = {};
assign(t) = {i,u,c2};
assign_elements(t,i,u,c2);assign_elements allows elementwise move or copy and avoids the
possible creation of temporaries in braced initializer constructs.
Now, let's look at tupl access alternatives:
get<I>(t)provides the usual indexed access.getie<I...>(t)is a combinedgetandtie
(fromtupl/tupl_tie.hpp)
tupl elements are all public and can be accessed directly by member id,
by structured binding (as we've seen) or by pointer-to-member:
t.x0 = {1}; // Direct access by known member id
get<1>(t) = {2}; // Usual indexed get<I> access
constexpr auto first = lml::mptr<0,decltype(t)>;
// = &tupl<int,unsigned,char[4]>::x0
t.*first = {0}; // Access via a member-pointer
get<2>(t) = {"3"}; // FAIL: can't assign arraysNote the use of braces on the right hand sides, to catch conversions.
The workaround for array member access is to use assign or getie:
lml::assign(get<2>(t)) = {"3"}; // array assign
getie<2>(t) = {"3"}; // From <tupl/tupl_tie.hpp>map is a helper function for iterating tupl elements as a variadic pack.
It's a generic access facility for implementing other accessors.
map is a
hidden friend function
defined within tupl classes.
As such, map is hidden from ordinary name lookup and can only be found
by ADL,
argument dependent lookup, when passed a tupl argument.
map(t,f) calls functor f with tupl t's elements xs... as arguments:
map(t,f) -> f(xs...)
The functor f will usually accept xs... as a variadic argument pack.
This map example adds 3 to each element of a tupl t:
lml::tupl t = {1,2U,3L};
map(t, [](auto&...xs){
((xs += 3), ...);
}
);
assert((t == lml::tupl{4,5U,6L}));The fold expression ((xs += 3), ...) unrolls to evaluate as
((t.x0+=3), (t.x1+=3), (t.x2+=3)) - the xs... argument pack
is unfolded by ..., which then expands the comma operator expression.
As a fuller example of map usage, here's a tupl stream printer:
#include <iostream>
using std::ostream;
#include "tupl.hpp"
using lml::tuplish; // Concept to accept tupl-likes
using lml::as_tupl_t; // Cast to embedded tupl type
ostream& operator<<(ostream& out, tuplish auto const& t)
{
return map(as_tupl_t(t), [&out](auto&...xs) -> auto&
{
char sep = '{';
auto sep_out = [&]{ // Output '{' on first call
out << sep; // Output ',' on following calls
[[maybe_unused]]static auto _(sep = ',');
};
return (out << ... << (sep_out(), xs)) << '}';
});
}The tuplish concept requires a member typedef tupl_t on which a
map function is defined. Prior to calling map, the as_tupl_t(t) cast
assures that the embedded type is used for lookup.
The simpler fold expression (out << ... << xs) unrolls as
(out << t.x0 << t.x1 << t.x2) to output each element in turn
(the sep_out function uses a 'cute' trick to update the separator char
sep from open-brace '{' to comma ',' as a side effect of its first call).
Here it is in use:
extern auto tupl_API(); // From the earlier snippet
int main()
{
std::cout << std::boolalpha << tupl_API();
}This prints the nested tupl return value as {{4,5,6},true}
ties type derives from tupl and adds a set of operator= overloads:
-
ties<Xs...>:tupl<Xs...>$+$ operator=
The use case is as a tuple-of-references specialized for assignments.
Tuples of references are used for:
- 'Collective assignment' to multiple variables.
- Forwarding of argument lists to functions.
- Lexicographic comparison of lists of variables.
In this library, each use case is best served by a specialized tupl-derived type.
(2.) is better handled by the fwds type
(c.f.
std::forward_as_tuple).
(3.) is better handled by thecmps type
A tie 'maker function' is the preferred way to make ties:
-
tie(xs...)$\rightarrow$ ties<decltype(xs)&...>
tie accepts only lvalues and deduces lvalues as assignment targets
and returns a ties tupl of lvalue references to its arguments.
#include "tupl_tie.hpp"
bool tie_API(int i, unsigned u, char(&c2)[2])
{
if (equals(lml::tie(i,u,c2), {})) // all elem==T{}
lml::tie(i,u,c2) = {1,2,"3"}; // assign-through
lml::tie(i,u,c2) = {}; // clear all to = {} init
char four[] = "4";
lml::tie(c2) = {four}; // assign from array lvalue
lml::tie(c2) = {{'5'}}; // assign from array rvalue
lml::tupl t = {1,2,"3"};
get<2>(t) = {"5"}; // FAILs to compile
getie<2>(t) = {"5"}; // assign to array element
getie<0,1>(t) = {3,4}; // multi-index get -> tie
return z;
}ties list-assignment is no longer builtin aggregate assignment.
t = {...} is implemented with similar semantics to the builtin;
the syntax extends nicely to reference ties just as for value tuples.
ties list-assignment improves on builtin aggregate assignment in
that it avoids creating temporaries in some cases, and it handles arrays.
However, it only handles all-move or all-copy assignments, not a mix.
ties also admits assignments from other tuplish types:
lml::tie(cp,mv) = lml::fwds{cc, std::move(mm)};Here, copy and move assignments are mixed by assigning from a
forwarding tuple that binds both lvalue and rvalue references.
-
tupl{v...}the basic tuple; Rule of Zero, deduces all values,
$\quad\quad\quad\quad\quad$ adds[[no_unique_address]]on members. -
lupl{v...}a basictuplwithout[[no_unique_address]].
tupl derived types ties, cmps & vals add specific operators:
ties{v...}deduces forwarding references, addsoperator=vals{v...}deduces all values, adds assignmentoperator=cmps{v...}deduces 'tupl_view' types, adds comparisons.
ties and vals implement heterogeneous assignments from any tuplish
type with compatible assignable types, plus improved list-assignment.
cmps implements heterogeneous comparisons with other tuplish types
whose corresponding elements are all comparable.
The intent of each type is reflected in its CTAD rules.
Four types add only CTAD, to deduce or constrain accepted types:
fwds{v...}deduces forwarding referenceslvals{v...}accepts lvalues only, rejects rvaluesrvals{v...}accepts rvalues only, rejects lvaluescvals{v...}deduces 'tupl_view' types;const&or values
(They add nothing else.)
tupl three-way comparison <=> is defaulted as a hidden friend function
(if that's possible, otherwise it's implemented out-of-class, if possible,
along with operator==) (in future all may be defined in-class).
tupl comparison operators are only defined for exact same-type tupls.
Heterogeneous or converting operations are provided by free functions:
compare3way(l,r)
equals(l,r)Note that the compare3way function accepts tupls of different size
(unlike the std equivalent).
Alternatively, the cmps derived tuplish type adds extra comparisons:
#include <string>
using std::string::operator""s;
tupl stringstd{"c++"s,20};
if (stringstd == cmps{+"c++",20}) ;Here the std::string element is compared with a char* value
(the + is needed to force decay because tupl always avoids decay).
tupl is always aggregate, regardless of element types:
using Up = tupl<unique_ptr>;
static_assert( is_aggregate<Up>() ); // alwaysThe properties of its element types propagate up to tupl:
tupl tup{1,2U,"3"}; // tupl<int,unsigned,char[2]>
using Tup = decltype(tup);
static_assert( is_trivially_copyable<Tup>()
&& is_standard_layout<Tup>()
&& is_trivial<Tup>()
&& is_regular<Tup>()
);Note that tupl is structural if all its element types are.
A type is structural if it can be used as a non-type template parameter:
template <auto> using is_structural = true_type;
static_assert( is_structural<Tup{}>());tupl layout has [[no_unique_address]] attribute on all members.
lupl is a 'layout-compatible' tupl without [[no_unique_address]].
Don't use tupl in multi-platform external APIs - it isn't portable.
Instead, use lupl where portable standard layout is required.
lupl is layout-compatible with an equivalent struct aggregate.
It's safe to reinterpret_cast a struct to a lupl for tuple-like access;
std::is_layout_compatible provides the required check:
struct Agg {int i; unsigned u; char c[2];} agg;
using Lup = lupl<int, unsigned, char[2]>;
static_assert(is_layout_compatible<Lup,Agg>());
// access agg as a tuple
get<1>(reinterpret_cast<Lup&>(agg)) = 2u;tupl is also layout compatible with an equivalent struct aggregate,
i.e. one with [[no_unique_address]] attribute on all members:
struct Agu { [[no_unique_address]] int i;
[[no_unique_address]] unsigned u;
[[no_unique_address]] char c[2];
};
static_assert(is_layout_compatible<Tup,Agu>());
static_assert(!is_layout_compatible<Tup,Agg>());Again: tupl layout is not guaranteed portable; clang and gcc differ in
layout and sizeof for some simple cases.
Don't use in external APIs
that may be compiled with different compilers, or compiler versions.
Use lupl instead.
As provided, tupl supports up to 16 elements.
Their member ids are the hex-digits of the index with x prefix:
x0, x1, x2, x3, x4, x5, x6, x7,
x8, x9, xa, xb, xc, xd, xe, xfmeson build option tupl_max_arity=32 reconfigures 'arity' to 32:
x0, x1, x2, x3, x4, x5, x6, x7,
x8, x9, xa, xb, xc, xd, xe, xf,
x10, x11, x12, x13, x14, x15, x16, x17,
x18, x19, x1a, x1b, x1c, x1d, x1e, x1fThe configured arity is reflected by lml::tupl_max_arity.
The expansion is implemented by the preprocessor and driven by
a preprocessor symbol TUPL_MAX_ARITY.
The member ids are not configurable, though that was considered.
-
tupl.hppprovides basic value tuple types,tuplandlupl,
tupl-derived types with CTAD,fwds,lvals,rvals&cvals,
accessors,get<I>,get<T>,tupl_mptr&tupl_mptrs,
comparison operators<=>,==, plusequals&compare3way
free functions,assign&assign_elements(nooperator=). -
tupl_traits.hppdefines concepts, traits and type-list tools
useful for type-list manipulation and for defining tuplish types. -
tupl_cat.hppdefinestupl_initandtupl_catfunctions. -
index_sequences.hppexposesinteger_sequencetools,
used bytupl_cat.
tupl-derived types:
tupl_tie.hppderives atiestupl for reference tuples
with added assignment operators, plustieandgetiefunctions.tupl_vals.hppderives avalstupl, with added assignment ops.tupl_cmps.hppderives acmps, tupl with added comparison ops.
(tupl_platform.hpp is an internal header of macros for portability.)
tupl_amalgam.hpp amalgamates all these library headers in one, along
with all required headers from the c_array_support library dependency.
The amalgam is auto-generated (so don't edit it!).
Sibling library dependencies:
c_array_supportfor C array assignment and comparison.IREPEAT(optional dependency, mostly for developers).
std C++ dependencies:
<concepts>forassignable_from,ranges::swap, etc.<compare>for three-wayoperator<=>comparisons, etc.<cstdint>is also required on MSVC, foruintptr_tonly.
Note: no dependence on <utility> or use of index_sequence;
tupl does not subscribe to the std 'tuple protocol'.
The #include dependencies are best seen in a diagram.
flowchart TD
subgraph tupl library
tupl_tie.hpp --> tupl.hpp
tupl_vals.hpp --> tupl.hpp
tupl_cmps.hpp --> tupl.hpp
tupl_cat.hpp --> tupl.hpp
tupl_cat.hpp --> index_sequences.hpp
tupl.hpp --> tupl_traits.hpp
tupl.hpp .-> markdown["<sub>tupl_impl/tupl_impl.pp<br>preprocess -> tupl_impl.hpp</sub>"]
end
subgraph c_array_support library
tupl.hpp -...-> c_array_compare.hpp
tupl_traits.hpp -..-> c_array_assign.hpp
c_array_compare.hpp --> std["#lt;compare#gt;"]
c_array_compare.hpp --> c_array_support.hpp
c_array_assign.hpp --> c_array_support.hpp
c_array_assign.hpp --> con["#lt;concepts#gt;"]
c_array_support.hpp --> util_traits.hpp
c_array_support.hpp --> ALLOW_ZERO_SIZE_ARRAY.hpp
util_traits.hpp --> type_traitsstd["#lt;type_traits#gt;"]
end
c_array_support
is a sibling library split out early in development of
tupl and published alongside in the Lemuriad GitHub organization.
IREPEAT
is also published in the Lemuriad GitHub organization.
See below for details of its use in preprocessor codegen.
tupl depends on non-standard compiler extensions.
integer-sequence builtins are used that are compiler-specific.
This is done to avoid dependency on std <integer_sequence>.
snitchlightweight C++20 testing frameworkmesonversion >= 0.64.0 (no CMake yet)- Python is required for meson build
bashshell, orgit-bashor similar on Windows,
to run theamalgamate.shshell script
A build setup is recommended, even for header-only projects, although
single header tupl_amalgam.hpp provides a quick way to get started.
meson.build scripts are provided
requiring meson >= v0.64.0
(there's no CMake support yet; contributions are welcome.
meson setup fetches all dependencies from GitHub repos automatically,
if they are not present, or they can be download manually if preferred.
git clone https://github.com/Lemuriad/tupl.git
cd tupl
meson setup build
meson compile -C build
meson test sanity -C build
meson test -C build- Fetch dependencies from github (via meson git-wraps)
- Marshall header paths
- Specify build flags and configuration options
- Preprocess
$\rightarrow$ tupl_impl.hpp - Generate
$\rightarrow$ tupl_amalgam.hpp - Build and run tests
- ToDo: Benchmarks and static analysis
See demo project
tupl_xampl
for an example client setup, using meson.
When the tupl library is used as a meson subproject dependency then test
targets are not set up by default, and the snitch library is not downloaded.
Use build configuration option -D tupl:tests=enabled if you want tests.
A custom target is added to auto-generate the tupl_amalgam.hpp header,
when build option tupl_codegen=enabled is configured (not by default).
The IREPEAT
preprocessor library is used to generate "tupl_impl.hpp"
by preprocessing tupl_impl/tupl_impl.pp" and component includes:
flowchart LR
tupl_impl[tupl_impl.pp<br>tupl_impl_noedit_warn.hpp<br>tupl_impl_assign.hpp<br>tupl_impl_compare.hpp] -->|preprocess<br>IREPEAT| tupl_impl.hpp
style tupl_impl text-align:left
It's possible to generate tupl_impl.hpp by direct preprocessor invocation,
e.g. from the root directory with these GCC options:
g++ -I. -Isubprojects/IREPEAT -Itupl_impl
-MMD -nostdinc -C -E -P
-o tupl_impl.hpp tupl_impl/tupl_impl.ppThe meson build scripts set up a target to auto-generate tupl_impl.hpp
initially, and on recompiles if the tupl_impl implementation files are edited.
meson v0.64.0 introduced support for preprocessor targets so this sets the
minimum version requirement.
Support is improved in more recent versions.
The build target doesn't appear to be invocable from the meson commandline.
It can be invoked from the ninja backend:
ninja -C build tupl_impl.hpptupl_amalgam.hpp is also a build target.
To generate the amalgamated header, run:
meson compile -C build tupl_amalgam.hpp-D tupl_max_arity configures the max number of elements.
-D no_lupl option omits the codegen of lupl specializations.
-D namespace_id configures the library namespace.
-D tests set =enabled or =disabled to control test targets.
-D tupl_codegen set =enabled for tupl_amalgam.hpp generation
or set =disabled to inhibit IREPEAT download.
The default configuration sets tupl_max_arity as 16 (== 0x10).
To configure a different max (use meson --wipe flag to reconfigure):
meson setup -D tupl_max_arity=24 buildWhen a proper variadic implementation of tupl becomes possible,
then max_arity will be made meaningless...
Preprocessor symbols implement the configuration options:
TUPL_MAX_ARITY sets the number of arity specializations.
NO_LUPL conditionally compiles-out lupl definition.
NAMESPACE_ID changes the default lml namespace.
TUPL_IMPL_PREPROCESS forces preprocessing on recompile.