Loading...
Searching...
No Matches
Classes | Namespaces | Functions
named.h File Reference

A wrapper for STL containers such as tuple, pair, and array. These containers force the programmer to remember the function/meaning of each element based on its position. This is error-prone. To mitigate this, the named wrapper allows to assign a name, via a template parameters, to each element so that subsequently it can be used to query elements by name, and to ensure that the assignment takes in account the order of the elements. More...

#include <tuple>
#include "concepts/util.h"
#include "util/constfor.h"
#include "util/string_literal.h"
#include "named_util.h"

Go to the source code of this file.

Classes

struct  named< T, NAMES >
 named container More...
 
struct  is_named< T >
 
struct  is_named< named< T, NAMES... > >
 
struct  named_equivalence< U, V >
 
struct  named_equivalence< U, V >
 
struct  tuple_size< bbm::named< T, NAMES... > >
 tuple_size specialization (to support structured binding) More...
 
struct  tuple_element< idx, bbm::named< T, NAMES... > >
 tuple_element specialization (to support structured binding) More...
 

Namespaces

namespace  bbm
 
namespace  std
 

Functions

template<string_literal... NAMES, typename T >
requires (sizeof...(NAMES) == 0) || (concepts::gettable<T> && (sizeof...(NAMES) == std::tuple_size_v<std::decay_t<T>>))
constexpr named< anonymize_t< T >, NAMES... > make_named (T &&t)
 Make a named of a gettable type (with size == #NAMES); renames if the type is a named container.
 
template<string_literal... NAMES, typename... Ts>
requires (sizeof...(NAMES) == sizeof...(Ts))
constexpr auto make_named (Ts &&... ts)
 Make a named tuple from a list of arguments (number of arguments == #NAMES)
 
template<string_literal... NAMES, typename T >
requires is_named_v<T>
constexpr auto pick (T &&t)
 Pick a subset/reshuffle a named container T and return as a named tuple.
 
template<string_literal... NAMES, typename... Ts>
requires (sizeof...(NAMES) == sizeof...(Ts))
named< std::tuple< Ts &... >, NAMES... > tie (Ts &&... src)
 Tie by name.
 
Get by name
template<string_literal NAME, string_literal... SUBNAME, typename T >
requires is_named_v<T>
constexpr decltype(auto) get (T &&src)
 
template<string_literal NAME, string_literal... SUBNAME, typename T >
requires is_named_v<T>
constexpr decltype(auto) get (const T &src)
 

Variables

type traits

@{

template<typename T >
constexpr bool is_named_v = is_named<std::decay_t<T>>::value
 
named_equivalence trait: do two types have the same set of names?
template<typename U , typename V >
static constexpr bool named_equivalence_v = named_equivalence<U,V>::value
 

anonymize trait: remove the names

template<typename T >
using anonymize_t = std::decay_t< decltype(anonymize_v(std::declval< T >()))>
 
template<typename T >
constexpr decltype(auto) anonymize_v (T &&t)
 

Detailed Description

A wrapper for STL containers such as tuple, pair, and array. These containers force the programmer to remember the function/meaning of each element based on its position. This is error-prone. To mitigate this, the named wrapper allows to assign a name, via a template parameters, to each element so that subsequently it can be used to query elements by name, and to ensure that the assignment takes in account the order of the elements.

named container elements vs structs** A struct cannot be defined in a function signature, and thus requires an external struct definition. This is cumbersome for single use structures. Structured bindings introduced in C++17 do not completely solve this problem, since the definition is still annonymous. Example:

struct foo { float a; float b; char c; };
foo bar1(void);

Alternatively, a single use tuple can be used:

std::tuple<float, float, char> bar2(void);

but now the meaning of each element is implicit (is 'a' the first or second float?). Named containers provide an alternative:

named<std::tuple<float,float,char>, "a", "b", "c"> bar3(void);

Equivallent correspondling look ups for the above 3 stratgies are:

auto b1 = bar1();
auto b2 = bar2();
auto b3 = bar3();
std::cout << b1.a << std::endl;
std::cout << std::get<0>(b2) << std::endl;
std::cout << get<"a">(b3) << std::endl;

While a bit more verbose, named containers are more closely related to structs, and thus less error prone.

Addtionally, named containers also allow for 'tie' with names:

float a, b; char c;
tie<"a", "b", "c">{a,b,c} = bar3();

The following will also take the order of names in account:

named<std::tuple<float, char, float>, "b", "c", "a"> b4 = bar3();
std::cout << get<"a">(b4) << " == " << get<"a">(b3) << std::endl;

Similarly there are 2 helper methods for making a named structure:

auto c1 = make_named<"a", "b", "c">( std::tuple<float,float,char>(1,2,'a') );
auto c2 = make_named<"a", "b", "c">(1, 2, 'a'); // produces a named tuple

To pick a subset of named elements use:

auto c3 = pick<"a", "c">(c2);

All of this can be combined with structured bindings:

auto [a, c] = pick<"a", "c">( foo3() );
auto [e,f,g] = foo3();

The former has the preference, since it allows to:

Named structures can also be recursive; a shortcut for get allows to directly query the recursive values:

auto c4 = make_named<"a">(make_named<"b", "c">('b', 'c'));
std::cout << get<"a">(c4) << std::endl; // regular get: (b = b, c = c)
std::cout << get<"a", "b">(c4) << std::endl; // recursive get: b

Note: a recusive named type wraps the inner named type in a tuple. Without wrapping, the names of the inner type are overwritten:

named< named<std::tuple<int,int>, "a", "b">, "c", "d" > t0{1,2}; // (c = 1, d = 2)
named< named<std::tuple<int,int>, "a", "b">, "c" > t1; // ERROR
named< std::tuple<named<std::tuple<int,int>, "c", "d">>, "e"> t2(t0); // (e = (c = 1, d = 2))
named< std::tuple<named<std::tuple<int,int>, "c", "d">>, "e", "f"> t3; // ERROR