carb/cpp/TypeTraits.h

File members: carb/cpp/TypeTraits.h

// Copyright (c) 2022-2024, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//

#pragma once

#include "../Defines.h"
#include "../detail/NoexceptType.h"
#include "detail/ImplInvoke.h"

#include <functional>
#include <type_traits>

namespace carb
{

namespace cpp
{

CARB_DETAIL_PUSH_IGNORE_NOEXCEPT_TYPE()

template <bool B>
using bool_constant = std::integral_constant<bool, B>;

namespace detail
{

template <typename... B>
struct conjunction_impl;

template <>
struct conjunction_impl<> : std::true_type
{
};

template <typename B>
struct conjunction_impl<B> : B
{
};

template <typename BHead, typename... BRest>
struct conjunction_impl<BHead, BRest...> : std::conditional_t<bool(BHead::value), conjunction_impl<BRest...>, BHead>
{
};

} // namespace detail

template <typename... B>
struct conjunction : detail::conjunction_impl<B...>
{
};

namespace detail
{

template <typename... B>
struct disjunction_impl;

template <>
struct disjunction_impl<> : std::false_type
{
};

template <typename B>
struct disjunction_impl<B> : B
{
};

template <typename BHead, typename... BRest>
struct disjunction_impl<BHead, BRest...> : std::conditional_t<bool(BHead::value), BHead, disjunction_impl<BRest...>>
{
};

} // namespace detail

template <typename... B>
struct disjunction : detail::disjunction_impl<B...>
{
};

template <typename B>
struct negation : bool_constant<!bool(B::value)>
{
};

template <class...>
using void_t = void;

using std::is_convertible;
using std::is_void;

namespace detail
{

// Base case matches where either `To` or `From` is void or if `is_convertible<From, To>` is false. The conversion in
// this case is only non-throwing if both `From` and `To` are `void`.
template <typename From, typename To, typename = void>
struct is_nothrow_convertible_impl : conjunction<is_void<From>, is_void<To>>
{
};

// If neither `From` nor `To` are void and `From` is convertible to `To`, then we test that such a conversion is
// non-throwing.
template <typename From, typename To>
struct is_nothrow_convertible_impl<
    From,
    To,
    std::enable_if_t<conjunction<negation<is_void<From>>, negation<is_void<To>>, is_convertible<From, To>>::value>>
{
    static void test(To) noexcept;

    static constexpr bool value = noexcept(test(std::declval<From>()));
};

} // namespace detail

template <typename From, typename To>
struct is_nothrow_convertible : bool_constant<detail::is_nothrow_convertible_impl<From, To>::value>
{
};

namespace detail
{

template <class T>
struct IsSwappable;
template <class T>
struct IsNothrowSwappable;
template <class T, class U, class = void>
struct SwappableWithHelper : std::false_type
{
};
using std::swap; // enable ADL
template <class T, class U>
struct SwappableWithHelper<T, U, void_t<decltype(swap(std::declval<T>(), std::declval<U>()))>> : std::true_type
{
};
template <class T, class U>
struct IsSwappableWith : bool_constant<conjunction<SwappableWithHelper<T, U>, SwappableWithHelper<U, T>>::value>
{
};
template <class T>
struct IsSwappable : IsSwappableWith<std::add_lvalue_reference_t<T>, std::add_lvalue_reference_t<T>>::type
{
};
template <class T, class U>
struct SwapCannotThrow : bool_constant<noexcept(swap(std::declval<T>(), std::declval<U>()))&& noexcept(
                             swap(std::declval<U>(), std::declval<T>()))>
{
};
template <class T, class U>
struct IsNothrowSwappableWith : bool_constant<conjunction<IsSwappableWith<T, U>, SwapCannotThrow<T, U>>::value>
{
};
template <class T>
struct IsNothrowSwappable : IsNothrowSwappableWith<std::add_lvalue_reference_t<T>, std::add_lvalue_reference_t<T>>::type
{
};

} // namespace detail

template <class T, class U>
struct is_swappable_with : detail::IsSwappableWith<T, U>::type
{
};
template <class T>
struct is_swappable : detail::IsSwappable<T>::type
{
};
template <class T, class U>
struct is_nothrow_swappable_with : detail::IsNothrowSwappableWith<T, U>::type
{
};
template <class T>
struct is_nothrow_swappable : detail::IsNothrowSwappable<T>::type
{
};

namespace detail
{

// The base case is matched in cases where `invoke_uneval` is an invalid expression. The `Qualify` is always set to void
// by users.
template <typename Qualify, typename Func, typename... TArgs>
struct invoke_result_impl
{
};

template <typename Func, typename... TArgs>
struct invoke_result_impl<decltype(void(invoke_uneval(std::declval<Func>(), std::declval<TArgs>()...))), Func, TArgs...>
{
    using type = decltype(invoke_uneval(std::declval<Func>(), std::declval<TArgs>()...));
};

template <typename Qualify, typename Func, typename... TArgs>
struct is_invocable_impl : std::false_type
{
};

template <typename Func, typename... TArgs>
struct is_invocable_impl<void_t<typename invoke_result_impl<void, Func, TArgs...>::type>, Func, TArgs...> : std::true_type
{
};

template <typename Qualify, typename Func, typename... TArgs>
struct is_nothrow_invocable_impl : std::false_type
{
};

template <typename Func, typename... TArgs>
struct is_nothrow_invocable_impl<void_t<typename invoke_result_impl<void, Func, TArgs...>::type>, Func, TArgs...>
    : bool_constant<noexcept(invoke_uneval(std::declval<Func>(), std::declval<TArgs>()...))>
{
};

} // namespace detail

template <typename Func, typename... TArgs>
struct invoke_result : detail::invoke_result_impl<void, Func, TArgs...>
{
};

template <typename Func, typename... TArgs>
using invoke_result_t = typename invoke_result<Func, TArgs...>::type;

template <typename Func, typename... TArgs>
struct is_invocable : detail::is_invocable_impl<void, Func, TArgs...>
{
};

template <typename Func, typename... TArgs>
struct is_nothrow_invocable : detail::is_nothrow_invocable_impl<void, Func, TArgs...>
{
};

namespace detail
{

template <typename Qualify, typename R, typename Func, typename... TArgs>
struct invocable_r_impl
{
    using invocable_t = std::false_type;

    using invocable_nothrow_t = std::false_type;
};

template <typename Func, typename... TArgs>
struct invocable_r_impl<std::enable_if_t<is_invocable<Func, TArgs...>::value>, void, Func, TArgs...>
{
    using invocable_t = std::true_type;

    using invocable_nothrow_t = is_nothrow_invocable<Func, TArgs...>;
};

// The is_void as part of the qualifier is to workaround an MSVC issue where it thinks this partial specialization and
// the one which explicitly lists `R` as void are equally-qualified.
template <typename R, typename Func, typename... TArgs>
struct invocable_r_impl<std::enable_if_t<is_invocable<Func, TArgs...>::value && !is_void<R>::value>, R, Func, TArgs...>
{
private:
    // Can't use declval for conversion checks, as it adds an rvalue ref to the type. We want to make sure the result of
    // a returned function can be converted.
    static invoke_result_t<Func, TArgs...> get_val() noexcept;

    template <typename Target>
    static void convert_to(Target) noexcept;

    template <typename TR, typename = decltype(convert_to<TR>(get_val()))>
    static std::true_type test(int) noexcept;

    template <typename TR>
    static std::false_type test(...) noexcept;

    template <typename TR, typename = decltype(convert_to<TR>(get_val()))>
    static bool_constant<noexcept(convert_to<TR>(get_val()))> test_nothrow(int) noexcept;

    template <typename TR>
    static std::false_type test_nothrow(...) noexcept;

public:
    using invocable_t = decltype(test<R>(0));

    using invocable_nothrow_t = decltype(test_nothrow<R>(0));
};

} // namespace detail

template <typename R, typename Func, typename... TArgs>
struct is_invocable_r : detail::invocable_r_impl<void, R, Func, TArgs...>::invocable_t
{
};

template <typename R, typename Func, typename... TArgs>
struct is_nothrow_invocable_r : detail::invocable_r_impl<void, R, Func, TArgs...>::invocable_nothrow_t
{
};

CARB_DETAIL_POP_IGNORE_NOEXCEPT_TYPE()

template <class T>
struct type_identity
{
    using type = T;
};

template <class T>
using type_identity_t = typename type_identity<T>::type;

template <class T>
struct remove_cvref
{
    using type = std::remove_cv_t<std::remove_reference_t<T>>;
};

template <class T>
using remove_cvref_t = typename remove_cvref<T>::type;

namespace detail
{
template <class T, class P, class = void>
struct IsConvertibleRange : public std::false_type
{
};

template <class T, class P>
struct IsConvertibleRange<T,
                          P,
                          cpp::void_t<decltype(std::declval<T>().size()),
                                      std::enable_if_t<std::is_convertible<decltype(std::declval<T>().data()), P>::value>>>
    : public std::true_type
{
};
} // namespace detail

} // namespace cpp
} // namespace carb