Skip to content

Commit

Permalink
SERVER-36900 Add a unique_function utility.
Browse files Browse the repository at this point in the history
The `mongo::unique_function` object is a move-only function object
which is useful for capturing move-only lambdas (such as those
with `std::unique_ptr` objects) into type-erased function objects
that resemble `std::function`.
  • Loading branch information
adamlsd committed Sep 6, 2018
1 parent 87235cc commit 0bb8465
Show file tree
Hide file tree
Showing 9 changed files with 657 additions and 39 deletions.
12 changes: 3 additions & 9 deletions src/mongo/executor/network_interface_tl.cpp
Expand Up @@ -235,17 +235,11 @@ Status NetworkInterfaceTL::startCommand(const TaskExecutor::CallbackHandle& cbHa
});
});

auto remainingWork = [
this,
state,
// TODO: once SERVER-35685 is done, stop using a `std::shared_ptr<Future>` here.
future = std::make_shared<decltype(pf.future)>(std::move(pf.future)),
baton,
onFinish
](StatusWith<std::shared_ptr<CommandState::ConnHandle>> swConn) mutable {
auto remainingWork = [ this, state, future = std::move(pf.future), baton, onFinish ](
StatusWith<std::shared_ptr<CommandState::ConnHandle>> swConn) mutable {
makeReadyFutureWith([&] {
return _onAcquireConn(
state, std::move(*future), std::move(*uassertStatusOK(swConn)), baton);
state, std::move(future), std::move(*uassertStatusOK(swConn)), baton);
})
.onError([](Status error) -> StatusWith<RemoteCommandResponse> {
// The TransportLayer has, for historical reasons returned SocketException for
Expand Down
72 changes: 66 additions & 6 deletions src/mongo/stdx/type_traits.h
Expand Up @@ -54,29 +54,89 @@ using enable_if_t = typename std::enable_if<B, T>::type;
} // namespace mongo
#endif

#if __cplusplus >= 201703

// TODO: Deal with importing this from C++20, when the time comes.
namespace mongo {
namespace stdx {

using std::void_t;
template <typename T>
struct type_identity {
using type = T;
};

template <typename T>
using type_identity_t = stdx::type_identity<T>;

} // namespace stdx
} // namespace mongo

#else

// TODO: Re-evaluate which of these we need when making the cutover to C++17.

namespace mongo {
namespace stdx {

namespace detail {
template <typename...>
struct make_void {
using type = void;
};
} // namespace detail

template <typename... Args>
using void_t = typename make_void<Args...>::type;
using void_t = typename detail::make_void<Args...>::type;

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

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

template <typename... B>
struct disjunction : std::false_type {};
template <typename B>
struct disjunction<B> : B {};
template <typename B1, typename... B>
struct disjunction<B1, B...> : std::conditional_t<bool(B1::value), B1, stdx::disjunction<B...>> {};

template <typename...>
struct conjunction : std::true_type {};
template <typename B>
struct conjunction<B> : B {};
template <typename B1, typename... B>
struct conjunction<B1, B...> : std::conditional_t<bool(B1::value), stdx::conjunction<B...>, B1> {};

namespace detail {
template <typename Func,
typename... Args,
typename = typename std::result_of<Func && (Args && ...)>::type>
auto is_invocable_impl(Func&& func, Args&&... args) -> std::true_type;
auto is_invocable_impl(...) -> std::false_type;
} // namespace detail

template <typename Func, typename... Args>
struct is_invocable
: decltype(detail::is_invocable_impl(std::declval<Func>(), std::declval<Args>()...)) {};

namespace detail {

// This helps solve the lack of regular void problem, when passing a 'conversion target' as a
// parameter.

template <typename R,
typename Func,
typename... Args,
typename ComputedResult = typename std::result_of<Func && (Args && ...)>::type>
auto is_invocable_r_impl(stdx::type_identity<R>, Func&& func, Args&&... args) ->
typename stdx::disjunction<std::is_void<R>,
std::is_same<ComputedResult, R>,
std::is_convertible<ComputedResult, R>>::type;
auto is_invocable_r_impl(...) -> std::false_type;
} // namespace detail

template <typename R, typename Func, typename... Args>
struct is_invocable_r
: decltype(detail::is_invocable_r_impl(
stdx::type_identity<R>(), std::declval<Func>(), std::declval<Args>()...)) {};

} // namespace stdx
} // namespace mongo
#endif
4 changes: 2 additions & 2 deletions src/mongo/transport/baton.h
Expand Up @@ -30,8 +30,8 @@

#include <memory>

#include "mongo/stdx/functional.h"
#include "mongo/transport/transport_layer.h"
#include "mongo/util/functional.h"
#include "mongo/util/future.h"
#include "mongo/util/time_support.h"

Expand Down Expand Up @@ -82,7 +82,7 @@ class Baton {
/**
* Executes a callback on the baton.
*/
virtual void schedule(stdx::function<void()> func) = 0;
virtual void schedule(unique_function<void()> func) = 0;

/**
* Adds a session, returning a future which activates on read/write-ability of the session.
Expand Down
7 changes: 5 additions & 2 deletions src/mongo/transport/baton_asio_linux.h
Expand Up @@ -71,6 +71,9 @@ class TransportLayerASIO::BatonASIO : public Baton {
::close(fd);
}

EventFDHolder(const EventFDHolder&) = delete;
EventFDHolder& operator=(const EventFDHolder&) = delete;

// Writes to the underlying eventfd
void notify() {
while (true) {
Expand Down Expand Up @@ -189,7 +192,7 @@ class TransportLayerASIO::BatonASIO : public Baton {
return true;
}

void schedule(stdx::function<void()> func) override {
void schedule(unique_function<void()> func) override {
stdx::lock_guard<stdx::mutex> lk(_mutex);

_scheduled.push_back(std::move(func));
Expand Down Expand Up @@ -407,7 +410,7 @@ class TransportLayerASIO::BatonASIO : public Baton {
stdx::unordered_map<const ReactorTimer*, decltype(_timers)::const_iterator> _timersById;

// For tasks that come in via schedule. Or that were deferred because we were in poll
std::vector<std::function<void()>> _scheduled;
std::vector<unique_function<void()>> _scheduled;
};

} // namespace transport
Expand Down
11 changes: 11 additions & 0 deletions src/mongo/util/SConscript
Expand Up @@ -68,6 +68,17 @@ env.CppUnitTest(
]
)

env.CppUnitTest(
target='unique_function_test',
source=[
'unique_function_test.cpp'
],
LIBDEPS=[
],
LIBDEPS_PRIVATE=[
],
)

env.CppUnitTest(
target='represent_as_test',
source=[
Expand Down
186 changes: 186 additions & 0 deletions src/mongo/util/functional.h
@@ -0,0 +1,186 @@
/**
* Copyright 2018 MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects
* for all of the code used other than as permitted herein. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you do not
* wish to do so, delete this exception statement from your version. If you
* delete this exception statement from all source files in the program,
* then also delete it in the license file.
*/

#pragma once

#include <functional>
#include <type_traits>

#include "mongo/stdx/type_traits.h"
#include "mongo/util/assert_util.h"

namespace mongo {
template <typename Function>
class unique_function;

/**
* A `unique_function` is a move-only, type-erased functor object similar to `std::function`.
* It is useful in situations where a functor cannot be wrapped in `std::function` objects because
* it is incapable of being copied. Often this happens with C++14 or later lambdas which capture a
* `std::unique_ptr` by move. The interface of `unique_function` is nearly identical to
* `std::function`, except that it is not copyable.
*/
template <typename RetType, typename... Args>
class unique_function<RetType(Args...)> {
private:
// `nilbase` is used as a base for the `nil` type, to prevent it from being an aggregate.
struct nilbase {
protected:
nilbase() = default;
};
// `nil` is used as a placeholder type in parameter lists for `enable_if` clauses. They have to
// be real parameters, not template parameters, due to MSVC limitations.
class nil : nilbase {
nil() = default;
friend unique_function;
};

public:
using result_type = RetType;

~unique_function() noexcept = default;
unique_function() = default;

unique_function(const unique_function&) = delete;
unique_function& operator=(const unique_function&) = delete;

unique_function(unique_function&&) noexcept = default;
unique_function& operator=(unique_function&&) noexcept = default;

void swap(unique_function& that) noexcept {
using std::swap;
swap(this->impl, that.impl);
}

friend void swap(unique_function& a, unique_function& b) noexcept {
a.swap(b);
}

// TODO: Look into creating a mechanism based upon a unique_ptr to `void *`-like state, and a
// `void *` accepting function object. This will permit reusing the core impl object when
// converting between related function types, such as
// `int (std::string)` -> `void (const char *)`
template <typename Functor>
/* implicit */
unique_function(
Functor&& functor,
// The remaining arguments here are only for SFINAE purposes to enable this ctor when our
// requirements are met. They must be concrete parameters not template parameters to work
// around bugs in some compilers that we presently use. We may be able to revisit this
// design after toolchain upgrades for C++17.
std::enable_if_t<stdx::is_invocable_r<RetType, Functor, Args...>::value, nil> = makeNil(),
std::enable_if_t<std::is_move_constructible<Functor>::value, nil> = makeNil(),
std::enable_if_t<!std::is_same<Functor, unique_function>::value, nil> = makeNil())
: impl(makeImpl(std::forward<Functor>(functor))) {}

unique_function(std::nullptr_t) noexcept {}

RetType operator()(Args... args) const {
invariant(static_cast<bool>(*this));
return impl->call(std::forward<Args>(args)...);
}

explicit operator bool() const noexcept {
return static_cast<bool>(this->impl);
}

// Needed to make `std::is_convertible<mongo::unique_function<...>, std::function<...>>` be
// `std::false_type`. `mongo::unique_function` objects are not convertible to any kind of
// `std::function` object, since the latter requires a copy constructor, which the former does
// not provide. If you see a compiler error which references this line, you have tried to
// assign a `unique_function` object to a `std::function` object which is impossible -- please
// check your variables and function signatures.
//
// NOTE: This is not quite able to disable all `std::function` conversions on MSVC, at this
// time.
template <typename Signature>
operator std::function<Signature>() const = delete;

private:
// The `nil` type cannot be constructed as a default function-parameter in Clang. So we use a
// static member function that initializes that default parameter.
static nil makeNil() {
return {};
}

struct Impl {
virtual ~Impl() noexcept = default;
virtual RetType call(Args&&... args) = 0;
};

// These overload helpers are needed to squelch problems in the `T ()` -> `void ()` case.
template <typename Functor>
static void callRegularVoid(const std::true_type isVoid, Functor& f, Args&&... args) {
// The result of this call is not cast to void, to help preserve detection of
// `[[nodiscard]]` violations.
f(std::forward<Args>(args)...);
}

template <typename Functor>
static RetType callRegularVoid(const std::false_type isNotVoid, Functor& f, Args&&... args) {
return f(std::forward<Args>(args)...);
}

template <typename Functor>
static auto makeImpl(Functor&& functor) {
struct SpecificImpl : Impl {
explicit SpecificImpl(Functor&& func) : f(std::move(func)) {}

RetType call(Args&&... args) override {
return callRegularVoid(std::is_void<RetType>(), f, std::forward<Args>(args)...);
}

std::decay_t<Functor> f;
};

return std::make_unique<SpecificImpl>(std::move(functor));
}

std::unique_ptr<Impl> impl;
};

template <typename Signature>
bool operator==(const unique_function<Signature>& lhs, std::nullptr_t) noexcept {
return !lhs;
}

template <typename Signature>
bool operator!=(const unique_function<Signature>& lhs, std::nullptr_t) noexcept {
return static_cast<bool>(lhs);
}

template <typename Signature>
bool operator==(std::nullptr_t, const unique_function<Signature>& rhs) noexcept {
return !rhs;
}

template <typename Signature>
bool operator!=(std::nullptr_t, const unique_function<Signature>& rhs) noexcept {
return static_cast<bool>(rhs);
}
} // namespace mongo

0 comments on commit 0bb8465

Please sign in to comment.