Skip to content

Commit cbca6bc

Browse files
jacobsacopybara-github
authored andcommitted
gmock-actions: provide a const reference when converting in ReturnAction.
It doesn't make semantic sense for the conversion to modify the input, and the fact that it's allowed to do so appears to have just been a historical accident. PiperOrigin-RevId: 448135555 Change-Id: Id10f17af38cf3947ee25fe10654d97527173ebfc
1 parent 5e6a533 commit cbca6bc

File tree

2 files changed

+67
-47
lines changed

2 files changed

+67
-47
lines changed

googlemock/include/gmock/gmock-actions.h

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,12 @@ struct is_callable_r_impl<void_t<call_result_t<F, Args...>>, R, F, Args...>
322322
template <typename R, typename F, typename... Args>
323323
using is_callable_r = is_callable_r_impl<void, R, F, Args...>;
324324

325+
// Like std::as_const from C++17.
326+
template <typename T>
327+
typename std::add_const<T>::type& as_const(T& t) {
328+
return t;
329+
}
330+
325331
} // namespace internal
326332

327333
// Specialized for function types below.
@@ -872,17 +878,14 @@ class ReturnAction final {
872878
public:
873879
explicit ReturnAction(R value) : value_(std::move(value)) {}
874880

875-
// Support conversion to function types with compatible return types. See the
876-
// documentation on Return for the definition of compatible.
877-
template <typename U, typename... Args>
881+
template <typename U, typename... Args,
882+
typename = typename std::enable_if<conjunction<
883+
// See the requirements documented on Return.
884+
negation<std::is_same<void, U>>, //
885+
negation<std::is_reference<U>>, //
886+
std::is_convertible<const R&, U>, //
887+
std::is_copy_constructible<U>>::value>::type>
878888
operator Action<U(Args...)>() const { // NOLINT
879-
// Check our requirements on the return type.
880-
static_assert(!std::is_reference<U>::value,
881-
"use ReturnRef instead of Return to return a reference");
882-
883-
static_assert(!std::is_void<U>::value,
884-
"Can't use Return() on an action expected to return `void`.");
885-
886889
return Impl<U>(value_);
887890
}
888891

@@ -918,27 +921,7 @@ class ReturnAction final {
918921
// that does `return R()` requires R to be implicitly convertible to
919922
// U, and uses that path for the conversion, even U Result has an
920923
// explicit constructor from R.
921-
//
922-
// We provide non-const access to input_value to the conversion
923-
// code. It's not clear whether this makes semantic sense -- what
924-
// would it mean for the conversion to modify the input value? This
925-
// appears to be an accident of history:
926-
//
927-
// 1. Before the first public commit the input value was simply an
928-
// object of type R embedded directly in the Impl object. The
929-
// result value wasn't yet eagerly created, and the Impl class's
930-
// Perform method was const, so the implicit conversion when it
931-
// returned the value was from const R&.
932-
//
933-
// 2. Google changelist 6490411 changed ActionInterface::Perform to
934-
// be non-const, citing the fact that an action can have side
935-
// effects and be stateful. Impl::Perform was updated like all
936-
// other actions, probably without consideration of the fact
937-
// that side effects and statefulness don't make sense for
938-
// Return. From this point on the conversion had non-const
939-
// access to the input value.
940-
//
941-
value(ImplicitCast_<U>(input_value)) {}
924+
value(ImplicitCast_<U>(internal::as_const(input_value))) {}
942925

943926
// A copy of the value originally provided by the user. We retain this in
944927
// addition to the value of the mock function's result type below in case
@@ -1763,7 +1746,7 @@ internal::WithArgsAction<typename std::decay<InnerAction>::type> WithoutArgs(
17631746
// * U is not void.
17641747
// * U is not a reference type. (Use ReturnRef instead.)
17651748
// * U is copy-constructible.
1766-
// * R& is convertible to U.
1749+
// * const R& is convertible to U.
17671750
//
17681751
// The Action<U(Args)...> object contains the R value from which the U return
17691752
// value is constructed (a copy of the argument to Return). This means that the

googlemock/test/gmock-actions_test.cc

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -654,8 +654,8 @@ TEST(ReturnTest, AcceptsStringLiteral) {
654654
TEST(ReturnTest, SupportsReferenceLikeReturnType) {
655655
// A reference wrapper for std::vector<int>, implicitly convertible from it.
656656
struct Result {
657-
std::vector<int>* v;
658-
Result(std::vector<int>& v) : v(&v) {} // NOLINT
657+
const std::vector<int>* v;
658+
Result(const std::vector<int>& v) : v(&v) {} // NOLINT
659659
};
660660

661661
// Set up an action for a mock function that returns the reference wrapper
@@ -709,6 +709,56 @@ TEST(ReturnTest, PrefersConversionOperator) {
709709
EXPECT_THAT(mock.AsStdFunction()(), Field(&Out::x, 19));
710710
}
711711

712+
// Return(x) should not be usable with a mock function result type that's
713+
// implicitly convertible from decltype(x) but requires a non-const lvalue
714+
// reference to the input. It doesn't make sense for the conversion operator to
715+
// modify the input.
716+
TEST(ReturnTest, ConversionRequiresMutableLvalueReference) {
717+
// Set up a type that is implicitly convertible from std::string&, but not
718+
// std::string&& or `const std::string&`.
719+
//
720+
// Avoid asserting about conversion from std::string on MSVC, which seems to
721+
// implement std::is_convertible incorrectly in this case.
722+
struct S {
723+
S(std::string&) {} // NOLINT
724+
};
725+
726+
static_assert(std::is_convertible<std::string&, S>::value, "");
727+
#ifndef _MSC_VER
728+
static_assert(!std::is_convertible<std::string&&, S>::value, "");
729+
#endif
730+
static_assert(!std::is_convertible<const std::string&, S>::value, "");
731+
732+
// It shouldn't be possible to use the result of Return(std::string) in a
733+
// context where an S is needed.
734+
using RA = decltype(Return(std::string()));
735+
736+
static_assert(!std::is_convertible<RA, Action<S()>>::value, "");
737+
static_assert(!std::is_convertible<RA, OnceAction<S()>>::value, "");
738+
}
739+
740+
// Return(x) should not be usable with a mock function result type that's
741+
// implicitly convertible from decltype(x) but requires an rvalue reference to
742+
// the input. We don't yet support handing over the value for consumption.
743+
TEST(ReturnTest, ConversionRequiresRvalueReference) {
744+
// Set up a type that is implicitly convertible from std::string&& and
745+
// `const std::string&&`, but not `const std::string&`.
746+
struct S {
747+
S(std::string&&) {} // NOLINT
748+
S(const std::string&&) {} // NOLINT
749+
};
750+
751+
static_assert(std::is_convertible<std::string, S>::value, "");
752+
static_assert(!std::is_convertible<const std::string&, S>::value, "");
753+
754+
// It shouldn't be possible to use the result of Return(std::string) in a
755+
// context where an S is needed.
756+
using RA = decltype(Return(std::string()));
757+
758+
static_assert(!std::is_convertible<RA, Action<S()>>::value, "");
759+
static_assert(!std::is_convertible<RA, OnceAction<S()>>::value, "");
760+
}
761+
712762
// Tests that Return(v) is covaraint.
713763

714764
struct Base {
@@ -760,19 +810,6 @@ TEST(ReturnTest, ConvertsArgumentWhenConverted) {
760810
<< "when performed.";
761811
}
762812

763-
class DestinationType {};
764-
765-
class SourceType {
766-
public:
767-
// Note: a non-const typecast operator.
768-
operator DestinationType() { return DestinationType(); }
769-
};
770-
771-
TEST(ReturnTest, CanConvertArgumentUsingNonConstTypeCastOperator) {
772-
SourceType s;
773-
Action<DestinationType()> action(Return(s));
774-
}
775-
776813
// Tests that ReturnNull() returns NULL in a pointer-returning function.
777814
TEST(ReturnNullTest, WorksInPointerReturningFunction) {
778815
const Action<int*()> a1 = ReturnNull();

0 commit comments

Comments
 (0)