Skip to content

Instantly share code, notes, and snippets.

@Ferdi265
Last active August 2, 2020 20:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Ferdi265/61258118526ec5e2411a971c215ed0fa to your computer and use it in GitHub Desktop.
Save Ferdi265/61258118526ec5e2411a971c215ed0fa to your computer and use it in GitHub Desktop.
C++17 Named Arguments
#pragma once
#include <type_traits>
#include <utility>
#include <tuple>
#include <optional>
namespace named_args {
// named argument value type
template <typename T, typename K>
struct arg {
using type = T;
using kind = K;
type value;
};
// named argument marker type
template <typename K>
struct marker {
template <typename T>
arg<decltype(std::forward<T>(std::declval<T&&>())), K> operator=(T&& t) const {
return {std::forward<T>(t)};
}
};
// named argument kinds
struct req_arg {
constexpr static bool required = true;
};
struct opt_arg {
constexpr static bool required = false;
using type = std::nullopt_t;
constexpr static type value = std::nullopt;
};
template <typename T, T _default>
struct def_arg {
constexpr static bool required = false;
using type = T;
constexpr static type value = _default;
};
namespace detail {
// append a type to a tuple
template <typename T, typename U>
struct tuple_append;
template <typename T, typename U>
using tuple_append_t = typename tuple_append<T, U>::type;
template <typename U, typename... Ts>
struct tuple_append<std::tuple<Ts...>, U> {
using type = std::tuple<Ts..., U>;
};
// prepend a type to a tuple
template <typename T, typename U>
struct tuple_prepend;
template <typename T, typename U>
using tuple_prepend_t = typename tuple_prepend<T, U>::type;
template <typename U, typename... Ts>
struct tuple_prepend<std::tuple<Ts...>, U> {
using type = std::tuple<U, Ts...>;
};
// check if a type is contained in a tuple
template <typename T, typename U>
struct tuple_contains;
template <typename T, typename U>
constexpr bool tuple_contains_v = tuple_contains<T, U>::value;
template <typename U>
struct tuple_contains<std::tuple<>, U> {
constexpr static bool value = false;
};
template <typename U, typename... Ts>
struct tuple_contains<std::tuple<U, Ts...>, U> {
constexpr static bool value = true;
};
template <typename U, typename T, typename... Ts>
struct tuple_contains<std::tuple<T, Ts...>, U> {
constexpr static bool value = tuple_contains_v<std::tuple<Ts...>, U>;
};
// count the number of occurrences of a type in a tuple
template <typename T, typename U>
struct tuple_count;
template <typename T, typename U>
constexpr size_t tuple_count_v = tuple_count<T, U>::value;
template <typename U>
struct tuple_count<std::tuple<>, U> {
constexpr static size_t value = 0;
};
template <typename U, typename... Ts>
struct tuple_count<std::tuple<U, Ts...>, U> {
constexpr static size_t value = 1 + tuple_count_v<std::tuple<Ts...>, U>;
};
template <typename U, typename T, typename... Ts>
struct tuple_count<std::tuple<T, Ts...>, U> {
constexpr static size_t value = tuple_count_v<std::tuple<Ts...>, U>;
};
// find the index of a type in a tuple
template <typename T, typename U>
struct tuple_index;
template <typename T, typename U>
constexpr size_t tuple_index_v = tuple_index<T, U>::value;
template <typename U, typename... Ts>
struct tuple_index<std::tuple<U, Ts...>, U> {
constexpr static size_t value = 0;
};
template <typename U, typename T, typename... Ts>
struct tuple_index<std::tuple<T, Ts...>, U> {
constexpr static size_t value = 1 + tuple_index_v<std::tuple<Ts...>, U>;
};
// transform a tuple of kinds into their default value types
template <typename K>
struct kind_types;
template <typename K>
using kind_types_t = typename kind_types<K>::type;
template <typename... Ks>
struct kind_types<std::tuple<Ks...>> {
using type = std::tuple<arg<typename Ks::type, Ks>...>;
};
// transform a tuple of kinds into their default values
template <typename K>
struct kind_values;
template <typename K>
constexpr kind_types_t<K> kind_values_v = kind_values<K>::value;
template <typename... Ks>
struct kind_values<std::tuple<Ks...>> {
static constexpr kind_types_t<std::tuple<Ks...>> value = {{Ks::value}...};
};
// get the kind of an argument
template <typename A>
struct arg_kind {
using type = void;
};
template <typename A>
using arg_kind_t = typename arg_kind<A>::type;
template <typename T, typename K>
struct arg_kind<arg<T, K>> {
using type = K;
};
// transform a tuple of arguments into a tuple of kinds
template <typename A>
struct arg_kinds;
template <typename A>
using arg_kinds_t = typename arg_kinds<A>::type;
template <typename... As>
struct arg_kinds<std::tuple<As...>> {
using type = std::tuple<arg_kind_t<As>...>;
};
// check for missing required arguments
template <typename K, typename A>
struct missing_req_args;
template <typename K, typename A>
using missing_req_args_t = typename missing_req_args<K, A>::type;
template <typename A>
struct missing_req_args<std::tuple<>, A> {
using type = std::tuple<>;
constexpr static bool empty = true;
};
template <typename A, typename K, typename... Ks>
struct missing_req_args<std::tuple<K, Ks...>, A> {
using type = std::conditional_t<
!K::required || tuple_contains_v<arg_kinds_t<A>, K>,
missing_req_args_t<std::tuple<Ks...>, A>,
tuple_prepend_t<missing_req_args_t<std::tuple<Ks...>, A>, K>
>;
constexpr static bool empty = std::is_same_v<type, std::tuple<>>;
};
// check for missing non-required arguments
template <typename K, typename A>
struct missing_non_req_args;
template <typename K, typename A>
using missing_non_req_args_t = typename missing_non_req_args<K, A>::type;
template <typename A>
struct missing_non_req_args<std::tuple<>, A> {
using type = std::tuple<>;
constexpr static bool empty = true;
};
template <typename A, typename K, typename... Ks>
struct missing_non_req_args<std::tuple<K, Ks...>, A> {
using type = std::conditional_t<
K::required || tuple_contains_v<arg_kinds_t<A>, K>,
missing_non_req_args_t<std::tuple<Ks...>, A>,
tuple_prepend_t<missing_non_req_args_t<std::tuple<Ks...>, A>, K>
>;
constexpr static bool empty = std::is_same_v<type, std::tuple<>>;
};
// check for duplicate arguments
template <typename K, typename A>
struct duplicate_args;
template <typename K, typename A>
using duplicate_args_t = typename duplicate_args<K, A>::type;
template <typename A>
struct duplicate_args<std::tuple<>, A> {
using type = std::tuple<>;
constexpr static bool empty = true;
};
template <typename A, typename K, typename... Ks>
struct duplicate_args<std::tuple<K, Ks...>, A> {
using type = std::conditional_t<
tuple_count_v<arg_kinds_t<A>, K> <= 1,
duplicate_args_t<std::tuple<Ks...>, A>,
tuple_prepend_t<duplicate_args_t<std::tuple<Ks...>, A>, K>
>;
constexpr static bool empty = std::is_same_v<type, std::tuple<>>;
};
// check for invalid arguments
template <typename K, typename A>
struct invalid_args;
template <typename K, typename A>
using invalid_args_t = typename invalid_args<K, A>::type;
template <typename K>
struct invalid_args<K, std::tuple<>> {
using type = std::tuple<>;
constexpr static bool empty = true;
};
template <typename K, typename A, typename... As>
struct invalid_args<K, std::tuple<A, As...>> {
using type = std::conditional_t<
tuple_contains_v<K, arg_kind_t<A>>,
invalid_args_t<K, std::tuple<As...>>,
tuple_prepend_t<invalid_args_t<K, std::tuple<As...>>, A>
>;
constexpr static bool empty = std::is_same_v<type, std::tuple<>>;
};
}
// error reporting type
template <typename missing_req_args, typename duplicate_args, typename invalid_args, bool valid = false>
struct error {
static_assert(valid, "named_args::error<missing_req_args, duplicate_args, invalid_args>: missing, duplicate, or invalid arguments");
};
namespace detail {
// instantiation of error checks
template <typename K, typename A>
struct check_args {
using missing_req_args = detail::missing_req_args_t<K, A>;
using duplicate_args = detail::duplicate_args_t<K, A>;
using invalid_args = detail::invalid_args_t<K, A>;
constexpr static bool valid =
detail::missing_req_args<K, A>::empty &&
detail::duplicate_args<K, A>::empty &&
detail::invalid_args<K, A>::empty;
constexpr static named_args::error<missing_req_args, duplicate_args, invalid_args, valid> error{};
};
}
template <auto impl, typename... Ks>
struct function {
private:
template <typename K, typename Rest, typename... Args>
static constexpr decltype(auto) select(std::tuple<Args&&...>& args, Rest& rest) {
using arg_kinds = detail::arg_kinds_t<std::tuple<Args...>>;
using rest_kinds = detail::arg_kinds_t<std::remove_const_t<Rest>>;
if constexpr (detail::tuple_contains_v<arg_kinds, K>) {
return std::get<detail::tuple_index_v<arg_kinds, K>>(args);
} else {
return std::get<detail::tuple_index_v<rest_kinds, K>>(rest);
}
}
public:
template <typename... Args>
constexpr decltype(auto) operator()(Args&&... a) const {
[[maybe_unused]] detail::check_args<std::tuple<Ks...>, std::tuple<std::remove_reference_t<Args>...>> check;
using rest_args = detail::missing_non_req_args_t<std::tuple<Ks...>, std::tuple<std::remove_reference_t<Args>...>>;
std::tuple<Args&&...> args = std::forward_as_tuple(std::forward<Args>(a)...);
detail::kind_types_t<rest_args> rest = detail::kind_values_v<rest_args>;
return impl(std::forward<decltype(select<Ks>(args, rest).value)>(select<Ks>(args, rest).value)...);
}
};
}
#include <iostream>
#include <string>
#include <functional>
#include "named_args.h"
using namespace std::literals;
// define argument types
struct name_t : public named_args::req_arg {};
struct age_t : public named_args::opt_arg {};
struct bufsiz_t : public named_args::def_arg<size_t, 4096> {};
struct nice_t : public named_args::opt_arg {};
// create named argument markers
constexpr named_args::marker<name_t> name;
constexpr named_args::marker<age_t> age;
constexpr named_args::marker<bufsiz_t> bufsiz;
constexpr named_args::marker<nice_t> nice;
// implementation
void test_impl(std::string name, std::optional<int> age, size_t bufsiz, std::optional<std::reference_wrapper<int>> nice) {
std::cout << "test:\n";
std::cout << "- name is " << name << "\n";
if (age) {
std::cout << "- age is " << *age << "\n";
} else {
std::cout << "- no age given\n";
}
std::cout << "- bufsiz is " << bufsiz << "\n";
if (nice) {
nice->get() = 42;
}
}
constexpr named_args::function<test_impl, name_t, age_t, bufsiz_t, nice_t> test{};
// tests
void foo(char * s, int a, int b, int& n) {
test(name = s, age = a, bufsiz = b, nice = n);
}
int main() {
int n;
test(name = "foo", age = 42, bufsiz = 8192);
test(bufsiz = 8192, name = "foo", age = 42);
test(name = "bar", age = 1337);
test(name = "baz"s, nice = n);
std::cout << "nice! " << n << "\n";
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment