//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// UNSUPPORTED: c++03

// <tuple>

// template <class... Types> class tuple;

// Check that the tuple-like ctors are properly disabled when the UTypes...
// constructor should be selected.
//
// See https://llvm.org/PR22806.

#include <cassert>
#include <memory>
#include <tuple>
#include <type_traits>

#include "test_macros.h"

template <class Tp>
using uncvref_t = typename std::remove_cv<typename std::remove_reference<Tp>::type>::type;

template <class Tuple, class = uncvref_t<Tuple>>
struct IsTuple : std::false_type {};

template <class Tuple, class ...Args>
struct IsTuple<Tuple, std::tuple<Args...>> : std::true_type {};

struct ConstructibleFromTupleAndInt {
  enum State { FromTuple, FromInt, Copied, Moved };
  State state;

  ConstructibleFromTupleAndInt(ConstructibleFromTupleAndInt const&) : state(Copied) {}
  ConstructibleFromTupleAndInt(ConstructibleFromTupleAndInt &&) : state(Moved) {}

  template <class Tuple, class = typename std::enable_if<IsTuple<Tuple>::value>::type>
  explicit ConstructibleFromTupleAndInt(Tuple&&) : state(FromTuple) {}

  explicit ConstructibleFromTupleAndInt(int) : state(FromInt) {}
};

struct ConvertibleFromTupleAndInt {
  enum State { FromTuple, FromInt, Copied, Moved };
  State state;

  ConvertibleFromTupleAndInt(ConvertibleFromTupleAndInt const&) : state(Copied) {}
  ConvertibleFromTupleAndInt(ConvertibleFromTupleAndInt &&) : state(Moved) {}

  template <class Tuple, class = typename std::enable_if<IsTuple<Tuple>::value>::type>
  ConvertibleFromTupleAndInt(Tuple&&) : state(FromTuple) {}

  ConvertibleFromTupleAndInt(int) : state(FromInt) {}
};

struct ConstructibleFromInt {
  enum State { FromInt, Copied, Moved };
  State state;

  ConstructibleFromInt(ConstructibleFromInt const&) : state(Copied) {}
  ConstructibleFromInt(ConstructibleFromInt &&) : state(Moved) {}

  explicit ConstructibleFromInt(int) : state(FromInt) {}
};

struct ConvertibleFromInt {
  enum State { FromInt, Copied, Moved };
  State state;

  ConvertibleFromInt(ConvertibleFromInt const&) : state(Copied) {}
  ConvertibleFromInt(ConvertibleFromInt &&) : state(Moved) {}
  ConvertibleFromInt(int) : state(FromInt) {}
};

int main(int, char**)
{
    // Test for the creation of dangling references when a tuple is used to
    // store a reference to another tuple as its only element.
    // Ex std::tuple<std::tuple<int>&&>.
    // In this case the constructors 1) 'tuple(UTypes&&...)'
    // and 2) 'tuple(TupleLike&&)' need to be manually disambiguated because
    // when both #1 and #2 participate in partial ordering #2 will always
    // be chosen over #1.
    // See PR22806  and LWG issue #2549 for more information.
    // (https://llvm.org/PR22806)
    using T = std::tuple<int>;
    std::allocator<int> A;
    { // rvalue reference
        T t1(42);
        std::tuple< T&& > t2(std::move(t1));
        assert(&std::get<0>(t2) == &t1);
    }
    { // const lvalue reference
        T t1(42);

        std::tuple< T const & > t2(t1);
        assert(&std::get<0>(t2) == &t1);

        std::tuple< T const & > t3(static_cast<T const&>(t1));
        assert(&std::get<0>(t3) == &t1);
    }
    { // lvalue reference
        T t1(42);

        std::tuple< T & > t2(t1);
        assert(&std::get<0>(t2) == &t1);
    }
    { // const rvalue reference
        T t1(42);

        std::tuple< T const && > t2(std::move(t1));
        assert(&std::get<0>(t2) == &t1);
    }
    { // rvalue reference via uses-allocator
        T t1(42);
        std::tuple< T&& > t2(std::allocator_arg, A, std::move(t1));
        assert(&std::get<0>(t2) == &t1);
    }
    { // const lvalue reference via uses-allocator
        T t1(42);

        std::tuple< T const & > t2(std::allocator_arg, A, t1);
        assert(&std::get<0>(t2) == &t1);

        std::tuple< T const & > t3(std::allocator_arg, A, static_cast<T const&>(t1));
        assert(&std::get<0>(t3) == &t1);
    }
    { // lvalue reference via uses-allocator
        T t1(42);

        std::tuple< T & > t2(std::allocator_arg, A, t1);
        assert(&std::get<0>(t2) == &t1);
    }
    { // const rvalue reference via uses-allocator
        T const t1(42);
        std::tuple< T const && > t2(std::allocator_arg, A, std::move(t1));
        assert(&std::get<0>(t2) == &t1);
    }
    // Test constructing a 1-tuple of the form tuple<UDT> from another 1-tuple
    // 'tuple<T>' where UDT *can* be constructed from 'tuple<T>'. In this case
    // the 'tuple(UTypes...)' ctor should be chosen and 'UDT' constructed from
    // 'tuple<T>'.
    {
        using VT = ConstructibleFromTupleAndInt;
        std::tuple<int> t1(42);
        std::tuple<VT> t2(t1);
        assert(std::get<0>(t2).state == VT::FromTuple);
    }
    {
        using VT = ConvertibleFromTupleAndInt;
        std::tuple<int> t1(42);
        std::tuple<VT> t2 = {t1};
        assert(std::get<0>(t2).state == VT::FromTuple);
    }
    // Test constructing a 1-tuple of the form tuple<UDT> from another 1-tuple
    // 'tuple<T>' where UDT cannot be constructed from 'tuple<T>' but can
    // be constructed from 'T'. In this case the tuple-like ctor should be
    // chosen and 'UDT' constructed from 'T'
    {
        using VT = ConstructibleFromInt;
        std::tuple<int> t1(42);
        std::tuple<VT> t2(t1);
        assert(std::get<0>(t2).state == VT::FromInt);
    }
    {
        using VT = ConvertibleFromInt;
        std::tuple<int> t1(42);
        std::tuple<VT> t2 = {t1};
        assert(std::get<0>(t2).state == VT::FromInt);
    }

  return 0;
}
