Skip to content
Merged
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
2 changes: 1 addition & 1 deletion include/fn/choice.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ struct choice<Ts...> : sum<Ts...> {
*/
template <typename T>
constexpr explicit choice(::std::in_place_type_t<T> d, auto &&...args) noexcept
requires has_type<T>
requires has_type<T> && detail::_initializable<T, decltype(args)...>
: _impl(d, FWD(args)...)
{
}
Expand Down
19 changes: 17 additions & 2 deletions include/fn/detail/pack_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ template <::std::size_t I, typename T> struct _element {
T v; // NOSONAR cpp:S6226 MSVC ignores the attribute
};

// Initializes one element. A reference element is spelled as a cast rather than `T{arg}`: the two
// are the same thing ([expr.type.conv]/1.3 defines the latter as direct-initializing an invented
// variable of type T, and [dcl.init.list]/3.9 initializes a reference from a single element whose
// type is reference-related to it, i.e. binds), but gcc reads the braced form as materializing a
// temporary and rejects the bind - while accepting the equivalent declaration. The cast keeps both
// compilers on the binding path.
template <typename T> [[nodiscard]] constexpr auto _make_element(auto &&...args) -> T
{
if constexpr (::std::is_reference_v<T>)
return T(FWD(args)...); // a single argument, so this is a cast expression: it binds
else
return T{FWD(args)...};
}

template <typename, typename... Ts> struct pack_impl;

template <typename... Ts> struct _pack_append;
Expand Down Expand Up @@ -86,9 +100,10 @@ struct pack_impl<::std::index_sequence<Is...>, Ts...> : _element<Is, Ts>... {
template <typename T, typename Self>
static constexpr auto _append(Self &&self, auto &&...args) noexcept //
-> pack_impl<::std::index_sequence<Is..., size>, Ts..., T>
requires(not _some_sum<T>) && (not _some_pack<T>) && ::std::is_constructible_v<T, decltype(args)...>
requires(not _some_sum<T>) && (not _some_pack<T>) && _initializable<T, decltype(args)...>
{
return {static_cast<apply_const_lvalue_t<Self, Ts &&>>(FWD(self)._element<Is, Ts>::v)..., T{FWD(args)...}};
return {static_cast<apply_const_lvalue_t<Self, Ts &&>>(FWD(self)._element<Is, Ts>::v)...,
_make_element<T>(FWD(args)...)};
}

template <typename T, typename Self>
Expand Down
12 changes: 12 additions & 0 deletions include/fn/detail/traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,21 @@
#define INCLUDE_FN_DETAIL_TRAITS

#include <type_traits>
#include <utility>

namespace fn::detail {

// The storage initializes an element as `T{args...}`, so a constraint on it must ask the same
// question: `is_constructible_v` spells parenthesized initialization, which for an aggregate
// performs no brace elision (`std::array<int, 3>` is not "constructible" from 3 ints) and permits
// narrowing where brace initialization rejects it. A reference element is not brace-initialized but
// bound, and `T{...}` for a reference `T` is not even a portable question to ask (gcc rejects it,
// clang accepts) - so that leg asks about the binding instead.
template <typename T, typename... Args>
concept _initializable //
= (::std::is_reference_v<T> && ::std::is_constructible_v<T, Args...>) //
Comment thread
Bronek marked this conversation as resolved.
|| ((not ::std::is_reference_v<T>) && requires { T{::std::declval<Args>()...}; });

Check warning on line 23 in include/fn/detail/traits.hpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use an argument to the requires clause to provide a named value instead.

See more on https://sonarcloud.io/project/issues?id=libfn_functional&issues=AZ9bOJmC3to842hjmlV0&open=AZ9bOJmC3to842hjmlV0&pullRequest=296

// Change any rvalue or empty value to prvalue, but leave lvalues unchanged.
// This is meant to find the type of data members which won't bind to rvalues.
template <typename T> extern T _as_value;
Expand Down
8 changes: 4 additions & 4 deletions include/fn/pack.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ template <typename... Ts> struct pack : detail::pack_impl<::std::index_sequence_
*/
template <typename T>
[[nodiscard]] constexpr auto append(::std::in_place_type_t<T>, auto &&...args) & noexcept -> append_type<T>
requires ::std::is_constructible_v<T, decltype(args)...>
requires detail::_initializable<T, decltype(args)...>
&& requires { append_type<T>{_impl::template _append<T>(*this, FWD(args)...)}; }
{
return {_impl::template _append<T>(*this, FWD(args)...)};
Expand All @@ -61,7 +61,7 @@ template <typename... Ts> struct pack : detail::pack_impl<::std::index_sequence_
*/
template <typename T>
[[nodiscard]] constexpr auto append(::std::in_place_type_t<T>, auto &&...args) const & noexcept -> append_type<T>
requires ::std::is_constructible_v<T, decltype(args)...>
requires detail::_initializable<T, decltype(args)...>
&& requires { append_type<T>{_impl::template _append<T>(*this, FWD(args)...)}; }
{
return {_impl::template _append<T>(*this, FWD(args)...)};
Expand All @@ -76,7 +76,7 @@ template <typename... Ts> struct pack : detail::pack_impl<::std::index_sequence_
*/
template <typename T>
[[nodiscard]] constexpr auto append(::std::in_place_type_t<T>, auto &&...args) && noexcept -> append_type<T>
requires ::std::is_constructible_v<T, decltype(args)...>
requires detail::_initializable<T, decltype(args)...>
&& requires { append_type<T>{_impl::template _append<T>(::std::move(*this), FWD(args)...)}; }
{
return {_impl::template _append<T>(::std::move(*this), FWD(args)...)};
Expand All @@ -91,7 +91,7 @@ template <typename... Ts> struct pack : detail::pack_impl<::std::index_sequence_
*/
template <typename T>
[[nodiscard]] constexpr auto append(::std::in_place_type_t<T>, auto &&...args) const && noexcept -> append_type<T>
requires ::std::is_constructible_v<T, decltype(args)...>
requires detail::_initializable<T, decltype(args)...>
&& requires { append_type<T>{_impl::template _append<T>(::std::move(*this), FWD(args)...)}; }
{
return {_impl::template _append<T>(::std::move(*this), FWD(args)...)};
Expand Down
7 changes: 5 additions & 2 deletions include/fn/sum.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ struct sum<Ts...> {
*/
template <typename T>
constexpr explicit sum(::std::in_place_type_t<T>, auto &&...args)
requires has_type<T>
requires has_type<T> && detail::_initializable<T, decltype(args)...>
: data(detail::make_variadic_union<T, data_t>(FWD(args)...)), index(detail::type_index<T, Ts...>)
{
}
Expand Down Expand Up @@ -616,6 +616,7 @@ template <typename T> explicit sum(T) -> sum<::std::remove_cvref_t<T>>;
* @return TODO
*/
[[nodiscard]] constexpr auto as_sum(auto &&src) -> decltype(auto)
requires(not some_in_place_type<decltype(src)>)
{
return sum<::std::remove_cvref_t<decltype(src)>>(FWD(src));
}
Expand All @@ -626,7 +627,9 @@ template <typename T> explicit sum(T) -> sum<::std::remove_cvref_t<T>>;
* @param src TODO
* @return TODO
*/
template <typename T> [[nodiscard]] constexpr auto as_sum(::std::in_place_type_t<T>, auto &&...args) -> decltype(auto)
template <typename T>
[[nodiscard]] constexpr auto as_sum(::std::in_place_type_t<T>, auto &&...args) -> decltype(auto)
requires detail::_initializable<T, decltype(args)...>
{
return sum<T>(::std::in_place_type<T>, FWD(args)...);
}
Expand Down
14 changes: 14 additions & 0 deletions tests/fn/choice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ struct NonCopyable final {
NonCopyable &operator=(NonCopyable const &) = delete;
};

template <typename S, typename T, typename... Args>
concept can_in_place = requires(Args... args) { S{std::in_place_type<T>, args...}; };

} // anonymous namespace

TEST_CASE("choice non-monadic functionality", "[choice]")
Expand Down Expand Up @@ -342,6 +345,17 @@ TEST_CASE("choice non-monadic functionality", "[choice]")
auto b = choice{std::in_place_type<NonCopyable>, 42};
static_assert(std::is_same_v<decltype(b), choice<NonCopyable>>);
}

WHEN("constraints")
{
static_assert(can_in_place<choice<NonCopyable>, NonCopyable, int>);
static_assert(not can_in_place<choice<NonCopyable>, int, int>); // int is not an alternative

// the constructor forwards to sum's, and rejects what sum rejects
static_assert(not can_in_place<choice<NonCopyable>, NonCopyable>); // no default ctor
static_assert(not can_in_place<choice<NonCopyable>, NonCopyable, char const *>); // not constructible from
SUCCEED();
}
}

WHEN("forwarding constructors (aggregate)")
Expand Down
38 changes: 38 additions & 0 deletions tests/fn/pack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include <catch2/catch_all.hpp>

#include <array>
#include <tuple>
#include <utility>

Expand Down Expand Up @@ -228,6 +229,37 @@ TEST_CASE("append value categories", "[pack][append]")
CHECK(std::move(s).append(B{30}).invoke(check));
}

WHEN("reference element")
{
// Evaluated, not merely decltype'd: a reference element must BIND to the argument, and the
// element-initializing expression is only instantiated when the call is actually made
C c{};
auto q = s.append(std::in_place_type<B &>, c);
static_assert(std::same_as<decltype(q), T::append_type<B &>>);
CHECK(q.invoke([&c](int, std::string_view, A, B &b) { return &b == static_cast<B *>(&c); }));

auto r = s.append(c); // deduced, so the element type is C &
static_assert(std::same_as<decltype(r), T::append_type<C &>>);
CHECK(r.invoke([&c](int, std::string_view, A, C &x) { return &x == &c; }));

c.v = 77; // the same object, observed through both packs
CHECK(q.invoke([](int, std::string_view, A, B &b) { return b.v == 77; }));
CHECK(r.invoke([](int, std::string_view, A, C &x) { return x.v == 77; }));

WHEN("constexpr")
{
static_assert([] {
fn::pack<int> p{1};
B b{5, 6};
auto q = p.append(std::in_place_type<B &>, b);
auto r = p.append(b);
b.v = 9;
return q.invoke([](int, B &x) { return x.v == 9; }) && r.invoke([](int, B &x) { return x.v == 9; });
}());
SUCCEED();
}
}

WHEN("pack on the right side, deduced")
{
constexpr fn::pack<bool, int, B> a{true, 3, B{14}};
Expand All @@ -251,6 +283,12 @@ TEST_CASE("append value categories", "[pack][append]")
static_assert(can_append_in_place<T &, B, int, int>);
static_assert(not can_append_in_place<T &, B, char const *>); // B is not constructible from it

// the element is brace-initialized, so an aggregate is appended element-wise, exactly as `sum`
// constructs one - a constraint spelled with is_constructible_v would reject this
static_assert(can_append_in_place<T &, std::array<int, 3>, int, int, int>);
static_assert(not can_append_in_place<T &, std::array<int, 3>, int, int, int, int>); // one too many
static_assert(not can_append_in_place<T &, int, double>); // narrowing

// in_place_type selects the element type, it is never itself an element: with no arguments and
// no default constructor there is nothing to construct, and the deduced-Arg overload must not
// pick the call up and append the tag instead
Expand Down
46 changes: 46 additions & 0 deletions tests/fn/sum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ template <fn::some_sum auto S> auto read_nttp()
{
return S.invoke([](auto const &...args) { return (0.0 + ... + static_cast<double>(args)); });
}

template <typename S, typename T, typename... Args>
concept can_in_place = requires(Args... args) { S{std::in_place_type<T>, args...}; };

template <typename T, typename... Args>
concept can_as_sum = requires(Args... args) { fn::as_sum(std::in_place_type<T>, args...); };

template <typename T>
concept can_as_sum_value = requires(T v) { fn::as_sum(FWD(v)); };
} // anonymous namespace

TEST_CASE("sum basic functionality tests", "[sum]")
Expand All @@ -63,6 +72,20 @@ TEST_CASE("sum basic functionality tests", "[sum]")
constexpr auto b = fn::as_sum(std::in_place_type<long>, 12);
static_assert(std::same_as<decltype(b), fn::sum<long> const>);
static_assert(b == fn::sum{12l});

WHEN("constraints")
{
static_assert(can_as_sum<long, int>);
static_assert(can_as_sum<std::array<int, 3>, int, int, int>); // an aggregate, brace-initialized
static_assert(not can_as_sum<NonCopyable>); // no default constructor
static_assert(not can_as_sum<NonCopyable, char const *>); // not constructible from it

// the tag selects the alternative, it is never itself one: with nothing to construct there is
// no viable lift at all, rather than a sum whose alternative is the tag
static_assert(not can_as_sum_value<std::in_place_type_t<NonCopyable> const &>);
static_assert(can_as_sum_value<long>);
SUCCEED();
}
}

WHEN("sum_for")
Expand Down Expand Up @@ -207,10 +230,33 @@ TEST_CASE("sum basic functionality tests", "[sum]")
auto b = sum{std::in_place_type<NonCopyable>, 42};
static_assert(std::is_same_v<decltype(b), sum<NonCopyable>>);
}

WHEN("constraints")
{
static_assert(can_in_place<sum<NonCopyable>, NonCopyable, int>);
static_assert(not can_in_place<sum<NonCopyable>, int, int>); // int is not an alternative

// an argument list the alternative cannot be constructed from is not viable - viability must
// answer here, not fail to compile inside variadic_union, beyond SFINAE's reach
static_assert(not can_in_place<sum<NonCopyable>, NonCopyable>); // no default ctor
static_assert(not can_in_place<sum<NonCopyable>, NonCopyable, char const *>); // not constructible from
SUCCEED();
}
}

WHEN("forwarding constructors (aggregate)")
{
WHEN("constraints")
{
using T = std::array<int, 3>;
// the element is brace-initialized, which elides braces for an aggregate - a constraint
// spelled with is_constructible_v (parenthesized init) would reject this very construction
static_assert(can_in_place<sum<T>, T, int, int, int>);
static_assert(not can_in_place<sum<T>, T, int, int, int, int>); // one too many
static_assert(not can_in_place<sum<int>, int, double>); // narrowing, rejected by braces
SUCCEED();
}

WHEN("regular")
{
sum<std::array<int, 3>> a{std::in_place_type<std::array<int, 3>>, 1, 2, 3};
Expand Down