Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions include/xtensor/utils/xexception.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,17 @@ namespace xt
#define XTENSOR_ASSERT(expr)
#endif

namespace detail
{
// True when a failing XTENSOR_ASSERT throws. A function containing an
// XTENSOR_ASSERT can only be noexcept when this is false.
#if defined(XTENSOR_ENABLE_ASSERT) && !defined(XTENSOR_DISABLE_EXCEPTIONS)
inline constexpr bool assert_can_throw = true;
#else
inline constexpr bool assert_can_throw = false;
#endif
}

#ifdef XTENSOR_ENABLE_CHECK_DIMENSION
#define XTENSOR_CHECK_DIMENSION(S, ARGS) XTENSOR_TRY(check_dimension(S, ARGS))
#else
Expand Down
19 changes: 17 additions & 2 deletions include/xtensor/utils/xutils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <xtl/xtype_traits.hpp>

#include "../core/xtensor_config.hpp"
#include "xexception.hpp"

namespace xt
{
Expand All @@ -47,8 +48,22 @@ namespace xt
template <std::size_t I, class... Args>
constexpr decltype(auto) argument(Args&&... args) noexcept;

namespace detail
{
// Whether calling F on each element type S and converting the result to R cannot throw.
template <class R, class F, class... S>
inline constexpr bool
is_nothrow_apply_call_v = (noexcept(static_cast<R>(std::declval<F&>()(std::declval<const S&>()))) && ...);

// apply also checks the index and falls back to R{}.
template <class R, class F, class... S>
inline constexpr bool is_nothrow_apply_v = is_nothrow_apply_call_v<R, F, S...>
&& std::is_nothrow_default_constructible<R>::value
&& !assert_can_throw;
}

template <class R, class F, class... S>
R apply(std::size_t index, F&& func, const std::tuple<S...>& s) noexcept(noexcept(func(std::get<0>(s))));
R apply(std::size_t index, F&& func, const std::tuple<S...>& s) noexcept(detail::is_nothrow_apply_v<R, F, S...>);

template <class T, class S>
void nested_copy(T&& iter, const S& s);
Expand Down Expand Up @@ -252,7 +267,7 @@ namespace xt

template <class R, class F, class... S>
inline R
apply(std::size_t index, F&& func, const std::tuple<S...>& s) noexcept(noexcept(func(std::get<0>(s))))
apply(std::size_t index, F&& func, const std::tuple<S...>& s) noexcept(detail::is_nothrow_apply_v<R, F, S...>)
{
XTENSOR_ASSERT(sizeof...(S) > index);
return std::apply(
Expand Down
39 changes: 38 additions & 1 deletion test/test_xutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

#include <complex>
#include <initializer_list>
#include <stdexcept>
#include <string>
#include <tuple>
#include <type_traits>

Expand Down Expand Up @@ -113,9 +115,44 @@ namespace xt
return i;
};
auto t = std::make_tuple(1, 2, 3);
static_assert(noexcept(apply<int>(1, func_ne, t)));
// The bound check on the index throws when assertions are enabled.
static_assert(noexcept(apply<int>(1, func_ne, t)) == !detail::assert_can_throw);
}

struct throw_on_string
{
int operator()(int i) const noexcept
{
return i;
}

int operator()(const std::string&) const
{
throw std::runtime_error("throw_on_string");
}
};

TEST(utils, apply_propagates_exception)
{
// Only the call on the first element is noexcept: apply must not be.
auto t = std::make_tuple(1, std::string("a"));
static_assert(!noexcept(apply<int>(1, throw_on_string{}, t)));
EXPECT_EQ(apply<int>(0, throw_on_string{}, t), 1);
XT_EXPECT_THROW(apply<int>(1, throw_on_string{}, t), std::runtime_error);
}

#ifdef XTENSOR_ENABLE_ASSERT
TEST(utils, apply_out_of_range)
{
auto func_ne = [](int i) noexcept
{
return i;
};
auto t = std::make_tuple(1, 2, 3);
XT_EXPECT_THROW(apply<int>(3, func_ne, t), std::runtime_error);
}
#endif

TEST(utils, conditional_cast)
{
EXPECT_TRUE((std::is_same<decltype(conditional_cast<false, double>(1)), int>::value));
Expand Down
Loading