//===----------------------------------------------------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is dual licensed under the MIT and the University of Illinois Open
// Source Licenses. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++98, c++03, c++11

// <experimental/tuple>

// template <class F, class T> constexpr decltype(auto) apply(F &&, T &&)

// Test with different ref/ptr/cv qualified argument types.

#include <experimental/tuple>
#include <array>
#include <utility>
#include <cassert>

// std::array is explicitly allowed to be initialized with A a = { init-list };.
// Disable the missing braces warning for this reason.
#include "disable_missing_braces_warning.h"


namespace ex = std::experimental;

int call_with_value(int x, int y) { return (x + y); }
int call_with_ref(int & x, int & y) { return (x + y); }
int call_with_const_ref(int const & x, int const & y) { return (x + y); }
int call_with_rvalue_ref(int && x, int && y) { return (x + y); }
int call_with_pointer(int * x, int * y) { return (*x + *y); }
int call_with_const_pointer(int const* x, int const * y) { return (*x + *y); }


template <class Tuple>
void test_values()
{
    {
        Tuple t{1, 2};
        assert(3 == ex::apply(call_with_value, t));
    }
    {
        Tuple t{2, 2};
        assert(4 == ex::apply(call_with_ref, t));
    }
    {
        Tuple t{2, 3};
        assert(5 == ex::apply(call_with_const_ref, t));
    }
    {
        Tuple t{3, 3};
        assert(6 == ex::apply(call_with_rvalue_ref, static_cast<Tuple &&>(t)));
    }
    {
        Tuple const t{4, 4};
        assert(8 == ex::apply(call_with_value, t));
    }
    {
        Tuple const t{4, 5};
        assert(9 == ex::apply(call_with_const_ref, t));
    }
}

template <class Tuple>
void test_refs()
{
    int x = 0;
    int y = 0;
    {
        x = 1; y = 2;
        Tuple t{x, y};
        assert(3 == ex::apply(call_with_value, t));
    }
    {
        x = 2; y = 2;
        Tuple t{x, y};
        assert(4 == ex::apply(call_with_ref, t));
    }
    {
        x = 2; y = 3;
        Tuple t{x, y};
        assert(5 == ex::apply(call_with_const_ref, t));
    }
    {
        x = 3; y = 3;
        Tuple const t{x, y};
        assert(6 == ex::apply(call_with_value, t));
    }
    {
        x = 3; y = 4;
        Tuple const t{x, y};
        assert(7 == ex::apply(call_with_const_ref, t));
    }
}

template <class Tuple>
void test_const_refs()
{
    int x = 0;
    int y = 0;
    {
        x = 1; y = 2;
        Tuple t{x, y};
        assert(3 == ex::apply(call_with_value, t));
    }
    {
        x = 2; y = 3;
        Tuple t{x, y};
        assert(5 == ex::apply(call_with_const_ref, t));
    }
    {
        x = 3; y = 3;
        Tuple const t{x, y};
        assert(6 == ex::apply(call_with_value, t));
    }
    {
        x = 3; y = 4;
        Tuple const t{x, y};
        assert(7 == ex::apply(call_with_const_ref, t));
    }
}


template <class Tuple>
void test_pointer()
{
    int x = 0;
    int y = 0;
    {
        x = 2; y = 2;
        Tuple t{&x, &y};
        assert(4 == ex::apply(call_with_pointer, t));
    }
    {
        x = 2; y = 3;
        Tuple t{&x, &y};
        assert(5 == ex::apply(call_with_const_pointer, t));
    }
    {
        x = 3; y = 4;
        Tuple const t{&x, &y};
        assert(7 == ex::apply(call_with_const_pointer, t));
    }
}


template <class Tuple>
void test_const_pointer()
{
    int x = 0;
    int y = 0;
    {
        x = 2; y = 3;
        Tuple t{&x, &y};
        assert(5 == ex::apply(call_with_const_pointer, t));
    }
    {
        x = 3; y = 4;
        Tuple const t{&x, &y};
        assert(7 == ex::apply(call_with_const_pointer, t));
    }
}


int main()
{
    test_values<std::tuple<int, int>>();
    test_values<std::pair<int, int>>();
    test_values<std::array<int, 2>>();

    test_refs<std::tuple<int &, int &>>();
    test_refs<std::pair<int &, int &>>();

    test_const_refs<std::tuple<int const &, int const &>>();
    test_const_refs<std::pair<int const &, int const &>>();

    test_pointer<std::tuple<int *, int *>>();
    test_pointer<std::pair<int *, int *>>();
    test_pointer<std::array<int *, 2>>();

    test_const_pointer<std::tuple<int const *, int const *>>();
    test_const_pointer<std::pair<int const *, int const *>>();
    test_const_pointer<std::array<int const *, 2>>();
}
