named_types
named_types copied to clipboard
A C++14/17 implementation of named tuples
named_types
named_types is a C++14/1z named types implementation. It interacts well with the standard library. named_types is a header-only library. The current implementation offers the named_tuple facility and tools to manipulate compile-time strings. named_variant and named_any are planned.
named_types can be compiled with:
- GCC
5.3.1or higher - Clang
3.9or higher - ~~Visual C++
14.0.23107.0 D14RELor higher~~ not test with latest version
What can I do with it ?
- Write code more robust to future changes
- Make different parts of a software to communicate without sharing complex types
- Generate flexible factories with one line (see an example)
- Generate JSON data from statically defined structures without the boilerplate hassle (see an example)
- Project JSON data onto statically defined structures (see examples)
named_tuple
With literal operator template for strings (N3599), GCC and Clang only
#include <named_types/named_tuple.hpp>
#include <string>
#include <iostream>
#include <vector>
using namespace named_types;
namespace {
template <typename T, T... chars> constexpr named_tag<string_literal<T,chars...>> operator ""_t () { return {}; }
}
int main() {
auto test = make_named_tuple(
"nom"_t = std::string("Roger")
, "age"_t = 47
, "taille"_t = 1.92
, "liste"_t = std::vector<int>({1,2,3})
);
std::cout
<< "nom"_t(test) << "\n"
<< "age"_t(test) << "\n"
<< "taille"_t(test) << "\n"
<< "liste"_t(test).size() << std::endl;
std::get<decltype("nom"_t)>(test) = "Marcel";
++std::get<1>(test);
"taille"_t(test) = 1.93;
return 0;
}
####Standard C++14
#include <named_types/named_tuple.hpp>
#include <type_traits>
#include <string>
#include <iostream>
#include <vector>
using namespace named_types;
namespace {
size_t constexpr operator "" _h(const char* c, size_t s) { return const_hash(c); }
template <size_t HashCode>
constexpr named_tag<std::integral_constant<size_t,HashCode>> at() { return {}; }
template <size_t HashCode, class Tuple> constexpr decltype(auto)
at(Tuple&& in) { return at<HashCode>()(std::forward<Tuple>(in)); }
}
int main() {
auto test = make_named_tuple(
at<"name"_h>() = std::string("Roger")
, at<"age"_h>() = 47
, at<"size"_h>() = 1.92
, at<"list"_h>() = std::vector<int> {1,2,3}
);
std::cout
<< at<"name"_h>(test) << "\n"
<< at<"age"_h>(test) << "\n"
<< at<"size"_h>(test) << "\n"
<< at<"list"_h>(test).size()
<< std::endl;
std::get<decltype(at<"name"_h>())>(test) = "Marcel";
++std::get<1>(test);
at<"size"_h>(test) = 1.93;
return 0;
}
Introduction
The aim of a named_tuple is to provide compile time access to elements by name, as a classic struct would, and compile time access to elements by their index, as a std::tuple would.
named_tuple has no overhead as long as you enable inlining (most of the time by enabling optimizations). The overhead is still tiny when disabling inlining.
Features
Code readability
A named_tuple makes code cleaner since it provides more meaningful access for attributes and let code using them more robust when attributes are inserted or deleted.
Simple declaration
As named tuples are meant to store data, it is important to keep a readable declarative syntax to use them as members or as non-templated function arguments if needed.
named_tuple<std::string(name), int(age)> test;
Compliancy with std::tuple
A named_tuple inherits from std::tuple with no additional data member, making it as efficient as a std::tuple and higly compliant with source code using std::tuple.
auto test = make_named_tuple(
_<name>() = std::string("Roger")
, _<age>() = 47
);
std::tuple<std::string, int> tuple1(test);
named_tuple<std::string(name), int(age)> test2 = tuple1;
std::string name_val;
std::tie(name_val, std::ignore) = test2;
Tuple promotion
A given tuple can automatically be promoted to another if each common member is implicitly convertible.
void start(named_tuple<std::string(host), int(port)> const& conf) {
std::cout << "Host " << _<host>(conf) << " on port " << _<port>(conf) << "\n";
}
int main() {
start(make_named_tuple(_<host>() = std::string("mywebsite")));
start(make_named_tuple(_<port>() = 441u));
return 0;
}
Tuple injection
Promotion has a drawback : the values not extracted from the promoted tuple takes their default values. It is either impossible (no default constructor for instance) or unwanted. You can affect any named tuple to any other named tuple : common members will be copied or moved, uncommon members will be left untouched.
template <typename T> void configure(T&& values) {
// Default values
auto conf = make_named_tuple(
_<host>() = std::string("defaulthost")
, _<port>() = 80
);
// Inject values
conf = std::forward<T>(values);
std::cout << "Host " << _<host>(conf) << " on port " << _<port>(conf) << "\n";
}
int main() {
configure(make_named_tuple(_<host>() = std::string("mywebsite")));
configure(make_named_tuple(_<port>() = 441u));
return 0;
}
Compile time introspection
named_tuple members can be looped over at compile time.
size_t constexpr operator "" _s(const char* c, size_t s)
{ return basic_lowcase_charset_format::encode(c,s); }
template <size_t EncStr> constexpr named_tag<typename basic_lowcase_charset_format::decode<EncStr>::type>
at() { return {}; }
template <size_t EncStr, class Tuple> constexpr decltype(auto) at(Tuple&& in)
{ return at<EncStr>()(std::forward<Tuple>(in)); }
template <class Tuple> class Serializer {
std::ostringstream& output_;
public:
Serializer(std::ostringstream& output) : output_(output) {}
template <class Tag, class Type>
void operator() (Tag const&, Type const& value) {
output_
<< ((0 < Tuple::template tag_index<Tag>::value)?",":"")
<< '"' << typename Tag::value_type().str() << "\":\"" << value << "\"";
}
void stream(Tuple const& t) {
output_ << '{';
for_each(*this,t);
output_ << '}';
}
};
template <class Tuple> std::string Serialize(Tuple const& t) {
std::ostringstream output;
Serializer<Tuple>(output).stream(t);
return output.str();
}
int main() {
auto test = make_named_tuple(
at<"name"_s>() = std::string("Roger")
, at<"lastname"_s>() = std::string("Lefouard")
, at<"age"_s>() = 45
, at<"size"_s>() = 1.92f
);
std::cout << Serialize(test) << std::endl;
return 0;
}
Compile time strings
All over the examples, a use is made of a very simple templated string.
template <class Char, Char ...> string_literal;
This types is able to generate the corresponding char const*, its size, a hash code and to concatenate with its peers.
String literals with literal operator template
This is the easiest and cleanest way to use them, unfortunately, it is not standard C++ and is not compliant with Visual Studio. It can be used in Clang and GCC as a GNU extension.
template <typename T, T... chars> constexpr named_tag<string_literal<T,chars...>>
operator ""_t () { return {}; }
String literals encoded on unsigned integers
The named_types project provides a facility to encode strings on unsigned integers. For instance, a 64 bits unsigned integer can store up to 8 characters. By narrowing the charset, you can store more. named_types can encode any charset (up to 255 chars with Visual Studio, due to a constexpr limitation) on any unsigned integer. For instance, any string made of [0-9][a-b]-_ can be stored up to 12 characters on a 64 bits unsigned integer. If you add capital letters to it, it falls down to 10 characters. If you really need to use longer strings with this tool, they must be concatenated.
uint64_t constexpr operator "" _s(const char* c, size_t s)
{ return basic_lowcase_charset_format::encode(c,s); }
template <uint64_t EncStr> constexpr char const* decode() {
return typename basic_lowcase_charset_format::decode<EncStr>::type().str();
}
// ...
std::cout << decode<"atmost12char"_s>() << std::endl;
uint64_t constexpr operator "" _s(const char* c, size_t s)
{ return ascii_charset_format::encode(c,s); }
template <uint64_t EncStr> constexpr char const* decode() {
return typename ascii_charset_format::decode<EncStr>::type().str();
}
// ...
std::cout << decode<"Max9Char!"_s>() << std::endl;
You can use a charset of your own:
// Size of the longest string made of 'a' and 'b' storable on a uint32_t
std::cout << integral_string_format<uint32_t,char,'a','b'>::max_length_value << std:endl;
Build
You dont need to build anything to use it, named_tuple is header-only.
Build and run tests
Tests can be built and ran with CTest.
cmake ${named_types_dir}
make
make test
References
named_tuple is not the only project with such goals in mind. You migh consider the following resources:
- Discussions on StackOverflow and GoogleGroups
- Inline Object Declaration
If you are looking for similar things but at runtime (dynamic structures), head to this project and other resources referenced in it.