// g++ -std=c++11 -Wall -Wextra -Wpedantic -o pass_by pass_by.cpp


// Pass arguments by position or by name (typesafely, in C++11, without
// template recursion).

// This could be cleaned up a bit if C++11 were not a requirement:
// - `std::integer_sequence` and friends are part of C++14.
// - `std::conjunction` is part of C++17.
// - `std::initializer_list` could be replaced by fold expressions in C++17.
// - `std::*<>::value` could be replaced by `std::*_v<>` in C++17.
// - `*_impl` could be internal templated lambdas in C++20.

// TODO: Try to change `*_impl` into internal C++11 lambdas using `auto` and
// `decltype`: <https://www.youtube.com/watch?v=FRkJCvHWdwQ&t=24m35s>. This
// probably won't work because template parameter packs *are not types*.

// TODO: Do we need to take `index_sequence` as function parameter? Can we not
// take it as (explicit) template parameter? I've seen a talk on when to use
// what, and why (in the context of `enable_if`). Look it up.


#include <tuple>
#include <utility>
#include <iostream>


// C++14 `std::index_sequence`, `std::make_index_sequence`.
template<std::size_t... Is> struct index_sequence {};
template<std::size_t N, std::size_t... Is>
struct make_index_sequence : make_index_sequence<N-1, N-1, Is...> {};
template<std::size_t... Is>
struct make_index_sequence<0, Is...> : index_sequence<Is...> {};

// C++17 `std::conjunction`.
template<typename...> struct conjunction : std::true_type {};
template<typename B1> struct conjunction<B1> : B1 {};
template<typename B1, typename... Bn>
struct conjunction<B1, Bn...>
    : std::conditional<bool(B1::value), conjunction<Bn...>, B1>::type {};


// template<typename Key, typename Value>
// void print(Key key, Value value) {
//     std::cout << key << ": " << value << std::endl;
// }

void print(int key, std::string value) {
    std::cout << key << ": " << value << std::endl;
}

void print(std::string key, std::string value) {
    std::cout << key << ": " << "(string) " << value << std::endl;
}

void print(std::string key, int value) {
    std::cout << key << ": " << "(int) " << value << std::endl;
}


template<std::size_t... Is, typename... Args>
void pass_by_position_impl(index_sequence<Is...>, Args const &... args) {
    (void)std::initializer_list<int>{ (print(Is, args), 0) ... };
}

template<std::size_t... Is, typename... Args>
void pass_by_pair_impl(index_sequence<Is...>, std::pair<std::string, Args> const &... args) {
    (void)std::initializer_list<int>{ (print(args.first, args.second), 0) ... };
}

template<std::size_t... Is, typename... Args>
void pass_by_name_impl(index_sequence<Is...>, Args const &... args) {
    auto args_tuple = std::tie(args...);
    static_assert(
        sizeof...(Args) % 2 == 0,
        "pass_by_name takes an even number of arguments"
    );
    static_assert(
        conjunction<
            std::is_convertible<
                decltype(std::get<2*Is+0>(args_tuple)),
                std::string
            > ...
        >::value,
        "pass_by_name even arguments must be convertible to std::string"
    );
    (void)std::initializer_list<int>{
        (
            print(
                std::get<2*Is+0>(args_tuple),
                std::get<2*Is+1>(args_tuple)
            ),
            0
        ) ...
    };
}


template<typename... Args>
void pass_by_position(Args &&... args) {
    pass_by_position_impl(make_index_sequence<sizeof...(Args)>{}, args...);
}

template<typename... Args>
void pass_by_pair(std::pair<std::string, Args> const &... args) {
    pass_by_pair_impl(make_index_sequence<sizeof...(Args)>{}, args...);
}

template<typename... Args>
void pass_by_name(Args const &... args) {
    pass_by_name_impl(make_index_sequence<sizeof...(Args) / 2>{}, args...);
}


int main() {
    // 0: eeny
    // 1: meeny
    // 2: miny
    // 3: moe
    pass_by_position("eeny", "meeny", "miny", "moe");

    // Hello: (string) world
    // Answer: (int) 42
    pass_by_pair<std::string, int>({"Hello", "world"}, {"Answer", 42});

    // Hello: (string) world
    // Answer: (int) 42
    pass_by_name("Hello", "world", "Answer", 42);
}