Skip to content

Commit

Permalink
Make inplace_function's constructor SFINAE on non-callables.
Browse files Browse the repository at this point in the history
Before this patch, `is_convertible_v<int(*)(), inplace_function<int(int)>>`
was `true`. After this patch, it's `false`.

For modern guidance on the SFINAE requirements of function wrapper constructors,
I think the most up-to-date guideline is the `function_ref` paper:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0792r3.html#specification
Notice that `inplace_function` does not (yet) respect cv-qualified abominable types
as in `inplace_function<int(int) const>`, so, that wrinkle of the `function_ref` paper
doesn't apply to us.

Thanks to @Voultapher for the patch!
Committed by @Quuxplusone.

Fixes #149.
  • Loading branch information
Voultapher authored and Quuxplusone committed May 1, 2019
1 parent b2422f4 commit a53e9e6
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 6 deletions.
43 changes: 37 additions & 6 deletions SG14/inplace_function.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,39 @@ struct is_valid_inplace_dst : std::true_type
);
};

// C++11 MSVC compatible implementation of std::is_invocable_r.

template<class R> void accept(R);

template<class, class R, class F, class... Args> struct is_invocable_r_impl : std::false_type {};

template<class F, class... Args> struct is_invocable_r_impl<
decltype(std::declval<F>()(std::declval<Args>()...), void()),
void,
F,
Args...
> : std::true_type {};

template<class F, class... Args> struct is_invocable_r_impl<
decltype(std::declval<F>()(std::declval<Args>()...), void()),
const void,
F,
Args...
> : std::true_type {};

template<class R, class F, class... Args> struct is_invocable_r_impl<
decltype(accept<R>(std::declval<F>()(std::declval<Args>()...))),
R,
F,
Args...
> : std::true_type {};

template<class R, class F, class... Args> using is_invocable_r = is_invocable_r_impl<
void,
R,
F,
Args...
>;
} // namespace inplace_function_detail

template<
Expand Down Expand Up @@ -190,15 +223,13 @@ class inplace_function<R(Args...), Capacity, Alignment>
template<
typename T,
typename C = std::decay_t<T>,
typename = std::enable_if_t<!inplace_function_detail::is_inplace_function<C>::value>
typename = std::enable_if_t<
!inplace_function_detail::is_inplace_function<C>::value
&& inplace_function_detail::is_invocable_r<R, C, Args...>::value
>
>
inplace_function(T&& closure)
{
#if __cplusplus >= 201703L
static_assert(std::is_invocable_r<R, C, Args...>::value,
"inplace_function cannot be constructed from non-callable type"
);
#endif
static_assert(std::is_copy_constructible<C>::value,
"inplace_function cannot be constructed from non-copyable type"
);
Expand Down
100 changes: 100 additions & 0 deletions SG14_test/inplace_function_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,104 @@ static void test_return_by_move()
assert(InstrumentedCopyConstructor::moves == 1);
}

static int overloaded_function(stdext::inplace_function<int()>&& exec)
{
return exec();
}

static int overloaded_function(stdext::inplace_function<int(int)>&& exec)
{
return exec(42);
}

static void test_is_invocable()
{
using C_Int1 = int();
using C_Int2 = int(int);
using C_Void = void(int&);

using stdext::inplace_function_detail::is_invocable_r;

static_assert(is_invocable_r<int, C_Int1>::value, "");
static_assert(! is_invocable_r<int, C_Int2>::value, "");
static_assert(! is_invocable_r<int, C_Void>::value, "");

static_assert(is_invocable_r<int, C_Int2, int>::value, "");
static_assert(! is_invocable_r<int, C_Int1, int>::value, "");
static_assert(! is_invocable_r<int, C_Void, int>::value, "");

static_assert(is_invocable_r<void, C_Void, int&>::value, "");
static_assert(! is_invocable_r<void, C_Int1, int&>::value, "");

// Testing widening and narrowing conversions, and the "conversion" to void.
static_assert(is_invocable_r<void, C_Int1>::value, "");
static_assert(is_invocable_r<long, C_Int1>::value, "");
static_assert(is_invocable_r<char, C_Int1>::value, "");

// Testing the conversion from void to int, which should definitely not be allowed.
static_assert(! is_invocable_r<int, C_Void, int&>::value, "");

// cppreference:
// > Determines whether Fn can be invoked with the arguments ArgTypes...
// > to yield a result that is convertible to R.
//
// void is treated specially because a functions return value can be ignored.
static_assert(is_invocable_r<void, C_Int2, int&>::value, "");
static_assert(is_invocable_r<const void, C_Int2, int&>::value, "");

// Regression tests for both is_invocable and is_convertible.
static_assert(is_invocable_r<const int&, int()>::value, "");
static_assert(is_invocable_r<const int&, int(*)()>::value, "");
}

static void test_overloading()
{
struct NoDefaultCtor {
int val;
explicit NoDefaultCtor(int v) : val{v} {}
};

EXPECT_EQ(overloaded_function([]() -> int { return 3; }), 3);
EXPECT_EQ(overloaded_function([](int arg) -> int { return arg; }), 42);

using std::is_convertible;
using stdext::inplace_function;

const auto a = []() -> int { return 3; };
static_assert(is_convertible<decltype(a), inplace_function<int()>>::value, "");
static_assert(!is_convertible<decltype(a), inplace_function<int(int)>>::value, "");
static_assert(!is_convertible<decltype(a), inplace_function<void(int&)>>::value, "");

const auto b = [](int&) -> void {};
static_assert(is_convertible<decltype(b), inplace_function<void(int&)>>::value, "");
static_assert(!is_convertible<decltype(b), inplace_function<int()>>::value, "");
static_assert(!is_convertible<decltype(b), inplace_function<int(int)>>::value, "");

const auto c = [](int, NoDefaultCtor) -> int { return 3; };
static_assert(is_convertible<decltype(c), inplace_function<void(int, NoDefaultCtor)>>::value, "");
static_assert(!is_convertible<decltype(c), inplace_function<int()>>::value, "");
static_assert(!is_convertible<decltype(c), inplace_function<int(int)>>::value, "");

const auto d = []() -> void {};
static_assert(is_convertible<decltype(d), inplace_function<void()>>::value, "");
static_assert(!is_convertible<decltype(d), inplace_function<int()>>::value, "");
static_assert(!is_convertible<decltype(d), inplace_function<int(int)>>::value, "");

static_assert(is_convertible<int(), inplace_function<const int&()>>::value, "");
static_assert(is_convertible<int(*)(), inplace_function<const int&()>>::value, "");

// Same as a, but not const.
auto e = []() -> int { return 3; };
static_assert(is_convertible<decltype(e), inplace_function<int()>>::value, "");
static_assert(!is_convertible<decltype(e), inplace_function<int(int)>>::value, "");
static_assert(!is_convertible<decltype(e), inplace_function<void(int&)>>::value, "");

// Same as a, but not const and mutable.
auto f = []() mutable -> int { return 3; };
static_assert(is_convertible<decltype(f), inplace_function<int()>>::value, "");
static_assert(!is_convertible<decltype(f), inplace_function<int(int)>>::value, "");
static_assert(!is_convertible<decltype(f), inplace_function<void(int&)>>::value, "");
}

void sg14_test::inplace_function_test()
{
Expand Down Expand Up @@ -576,6 +674,8 @@ void sg14_test::inplace_function_test()
test_move_construction_from_smaller_buffer_is_noexcept();
test_is_convertible();
test_return_by_move();
test_is_invocable();
test_overloading();
}

#ifdef TEST_MAIN
Expand Down

0 comments on commit a53e9e6

Please sign in to comment.