bbm::named
One use of bbm::string_literal is to create named version of any type
that supports std::get (e.g., std::tuple, std::pair,
std::array, etc…).
bbm::named<std::tuple<int, float, char>, "a", "b", "c"> foo(1, 2.0, 'b');
std::cout << foo << std::endl;
The above example will create a 3-element tuple, and associate the name
(string_literal) “a” to the first element, “b” to the second, and “c” to
the last. The purpose of named tuples is two fold:
tuples are often used to return multi-value results from a function. However, a tuple requires that the programmer remembers the role of each element in the tuple, which can be error prone and might require the programmer to read the function implementation. Named tuples offer a convenient way to store a descriptive name with each tuple element.
C++does not allow for inline definition of a struct parameter or return type in a function declaration, requiring the struct to be defined separately.
Query
There several methods to query a element from a named tuple:
std::cout << std::get<0>(foo) << std::endl; // '1'
std::cout << bbm::get<"a">(foo) << std::endl; // '1'
A compile error is throws if the requested name does not exist in the
structure! Lookup and storage of names all occurs at compile time and there
is no runtime overhead. One can query the type with the value_type
typedef and the values of the value_type with the values() method:
std::cout << bbm::typestring< decltype(foo)::value_type > << std::endl;
std::cout << foo.values() << std::endl;
Named tuples also support structured bindings:
auto [a1, b1, c1] = foo;
std::cout << c1 << std::endl; // 'b'
auto [a2, c2] = bbm::pick<"a", "c">(foo);
std::cout << c2 << std::endl; // 'b'
as well as tying existing variables by name:
int a; float b; char c; bbm::tie<"a", "b", "c">{a,b,c} = foo;
Note that for both pick as well as tie the order of the names does not
need to correspond to the order in which they are defined. One can even repeat
a name.
Query names
named types allow to check if a name is present, or to query a name:
std::cout << foo::has_name<"d"> << std::endl; // false
std::cout << foo:names << std::endl; // yields a tuple: ("a", "b", "c")
std::cout << foo::template name<0> << std::endl; // yields "a" (use foo:size to require the length)
std::cout << foo::template find_name<"b"> << std::endl; // 1
Note find_name will return the position of the named in the value_type
structure. It will return a position beyond the last element if the name does
not exist (i.e., has_name == false).
bbm::make_named
A helper function is included to simplify creation of named tuples:
auto n1 = bbm::make_named<"a", "b", "c">( std::make_tuple(1, 2.0, 'b') );
auto n2 = bbm::make_named<"a", "b", "c">(1, 2.0, 'b');
In the former also works for other types that support std::get; the latter
always creates a named tuple.
Recursive named types
Named types can be recursive, and bbm::get supports recursive retrieval:
auto n3 = bbm::make_named<"a">( bbm::make_named<"b", "c">('b', 'c') );
std::cout << n3 << std::endl; // (a = (b = 'b', c = 'c'))
std::cout << bbm::get<"a">(n3) << std::endl; // (b = 'b', c = 'c')
std::cout << bbm::get<"b">( bbm::get<"a">( n3) ) << std::endl; // 'b'
std::cout << bbm::get<"a", "b">( n3 ) << std::endl; // 'b'
Care must be taken when constructing recursive named types:
bbm::named< bbm::named< std::tuple<int,int>, "a", "b">, "c", "d" > A{1, 2};
bbm::named< std::tuple< bbm::named< std::tuple<int,int>, "c", "d" >, "e" > B(A);
The first does not yield a recursive named type, it simply replaces the names
of the inner named type with those of the outer one (i.e., (c = 1, d =
2)). The latter is a true recursive named type yielding (e = (c = 1, d =
1)).
Type traits
To check if a type is a named type, you can use bbm::is_named_v<T>. To check
if two named types have the same set of names (but possibly in different
order): ‘’bbm::named_equivalence_v<T, U>’’.
To remove the names from a type (also works on non-named types, i.e., it returns the non-named type unaltered):
-
template<typename T>
using bbm::anonymize_t = std::decay_t<decltype(anonymize_v(std::declval<T>()))>
Additional Operations
Additional methods on named types are defined in util/named_util.h.
Copy by value
This method mirrors the corresponding method defined on tuples (defined in util/tuple.h, and it creates a copy of a named tuple by value (i.e., references are dereferenced):
-
template<typename TUP, string_literal... NAMES>
inline constexpr auto bbm::value_copy_named(const named<TUP, NAMES...> &src) value copy a named tuple
- Parameters:
src – = named tuple, possibly with references
- Returns:
a copy of the named tuple without references
Corresponding type-trait:
-
template<typename T>
using bbm::value_copy_named_t = decltype(value_copy_named(std::declval<T>())) type of value copying a named typle
Concat
-
template<typename ...T>
inline constexpr auto bbm::named_cat(T&&... t) cat named types
named_cat( named<std::tuple<…>, “A”, “B”>{a,b}, named<std::tuple<…>, “C”>{c} )
yields
named<std::tuple<…>, “A”, B”, “C”>{a,b,c}
Corresponding type-trait:
Subset
-
template<size_t START, size_t COUNT, typename NAMED>
inline constexpr auto bbm::subnamed(NAMED &&named) get a subset of a named tuple
- Template Parameters:
START – = index of first element
COUNT – = number of elements
- Parameters:
named – = named tuple
- Returns:
named tuple with elements [START, START+1, …, START+COUNT-1
Corresponding type-trait:
Prefix and Postfix
-
template<string_literal PREFIX, typename T, string_literal... NAMES>
inline constexpr auto bbm::prefix_names(named<T, NAMES...> t) prefix names in type
prefix_names<”BLA_”, named<std::tuple<…>, “A”, “B”>{a,b}
yields
named<std::tuple<…>, “BLA_A”, “BLA_B”>{a,b}
-
template<string_literal POSTFIX, typename T, string_literal... NAMES>
inline constexpr auto bbm::postfix_names(named<T, NAMES...> t) postfix names in type
postfix_names<”_BLA”, named<std::tuple<…>, “A”, “B”>{a,b}
yields
named<std::tuple<…>, “A_BLA”, “B_BLA”>{a,b}
Corresponding type-traits:
-
template<string_literal PREFIX, typename T>
using bbm::prefix_names_t = decltype(prefix_names<PREFIX>(std::declval<T>())) type of named tuple with pre-fixed name.
-
template<string_literal POSTFIX, typename T>
using bbm::postfix_names_t = decltype(post_names<POSTFIX>(std::declval<T>())) type of named tuple with post-fixed name.
Flatten
-
template<typename T>
inline constexpr auto bbm::named_flatten(T &&t) flatten a named type without merging names
named_flatten( named< std::tuple<named<std::tuple<float, char>, “A”, “B”>, int>, “C”, “D” > )
yields
named<std::tuple<float, char, int>, “A”, “B”, “D”>
-
template<string_literal SEP = ".", typename T>
inline constexpr auto bbm::merge_named_flatten(T &&t) flatten a named type with merging names
merge_named_flatten( named< std::tuple<named<std::tuple<float, char>, “A”, “B”>, int>, “C”, “D” > )
yields
named<std::tuple<float, char, int>, “C.A”, “C.B”, “D”>
The default seperator symbol is “.”, but this can be changed by passing a different symbols as the first template argument.
Sorting
BBM offers the following compile-time methods for sorting and checking is a named type is sorted:
-
template<typename NAMED>
static constexpr bool bbm::is_named_sorted_v = detail::is_named_sorted<NAMED>() true if named tuple is sorted by name
-
template<typename NAMED, size_t IDX = 0, typename PARTIAL = named<std::tuple<>>>
inline constexpr auto bbm::sort_named(NAMED &&named, PARTIAL &&partial = PARTIAL{}) sort a named tuple by name using insert-sort.
Details: this is a constexpr recursive method. It will attempt to insert the IDX-th element in PARTIAL, and then recurse to the IDX+1 element, until all elements are inserted.
- Parameters:
named – = named tuple to sort
- Template Parameters:
IDX – = start index of elements to sort; recommended default value = 0
PARTIAL – = partial solution to insert into; recommended default value = empty named tuple
- Returns:
sorted(NAMED[IDX..END], PARTIAL)
Run-time search
The typedef find_name on a named type allows to perform a linear search
for a given name at compile-time. If the named type is sorted, then one can
also perform the same search using a binary search:
-
template<string_literal NAME, typename NAMED>
static constexpr size_t bbm::binary_search_named_v = detail::binary_search_named<NAME, std::decay_t<NAMED>>() binary search a sorted named tuple
- Template Parameters:
NAME – = name to search for
NAMED – = named tuple
- Return:
index of best matching name (index of first name that is equal or larger)
In some case, the target name might not be known at compile time, and be
provided at run-time as an std::string. BBM offers tools for run-time
searching (linear is not sorted or binary if sorted):
-
template<typename NAMED, typename PROCESS, typename ...Ts>
inline auto bbm::linear_search_named(const std::string &str, NAMED &&named, PROCESS &&process, Ts&&... context)
-
template<typename NAMED, typename PROCESS, typename ...Ts>
inline auto bbm::binary_search_named(const std::string &str, NAMED &&named, PROCESS &&process, Ts&&... context)
Both methods have a similar signature. Searching in a potentially heteroegeous data structure (e.g., tuples) imposes a few constraints:
returning an index to the found element is not helpful. One can querry an element by its index in a tuple using
std::get. However, the index must be known at compile time.because the element might all have different types in a named type, returning the element itself is not an option either.
Therefore, both methods take a lambda function that takes an index as a template parameter, a reference to the string, named type and any number of ‘context’ arguments. The latter are used to pass any information to each call. These same context variables are also passed to the search method and passed without modification to the lambda:
auto n = bbm::make_named<"a", "b", "c">(1.0, 'b', 3);
bbm::binary_search_named( "c", n, []<size_t IDX>(const std::string& str, auto&& naned_type, float ctx) { std::cout << IDX << ", " << ctx << std::endl; }, 123.45);
The above code will output ‘3, 123.45’ (the position of the search named (“c”) and the value of the context variable. Optionally, the lambda may return a value as long as it returns the same type for each possible IDX value.
In case of a binary search, the lambda is called with the index of where the element should be added in the named type without breaking the sort. It is the responsibility of the programmer to check:
if an exact match is required, that the name of the corresponding element has the correct name:
std::get<IDX>(std::forward<decltype(named_type)>(named_type)) == str
if the returned index falls within the bounds of the named type; it is possible that the requested name would be inserted at the end:
IDX < std::tuple_size_v<decltype(named_type)>
In case of a linear search, if the name is not found, index is set to the size of the type (case 2 above).
Warning
The generated code will create an instance of the lambda method for each possible index value! This is the case for both the linear and binary search. Hence, if a binary search is performed on a named tuple with 32 elements, then 33 calls (one for each element plus one for first invalid index value) are generated. Hence, it is advisable to keep the lambda function as compact as possible.