Skip to content

Commit

Permalink
More comprehensive tests of Visit as well as Visitor
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul-Licameli committed Apr 16, 2023
1 parent 7d73fa1 commit ce02e4f
Showing 1 changed file with 149 additions and 40 deletions.
189 changes: 149 additions & 40 deletions libraries/lib-utility/tests/VariantTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,85 +11,194 @@
#include <catch2/catch.hpp>

#include "Variant.h"
using namespace Variant;

enum Ref{ lvalue, rvalue, noref };

namespace {
// Not yet varied in these tests
using ResultType = int;
struct ValueType {
int x;
operator int() const { return x; }
};
// Also works with
// using ValueType = int;

//! Parameterize for the type of visitor return
template<bool Const, Ref ref = lvalue>
struct Tester {

template<typename T>
using MaybeConst = std::conditional_t<Const, const T, T>;

using ResultType = std::conditional_t<ref == noref,
MaybeConst<ValueType>,
std::conditional_t<ref == rvalue,
std::add_rvalue_reference_t<MaybeConst<ValueType>>,
std::add_lvalue_reference_t<MaybeConst<ValueType>>
>
>;

template<int Value>
static ResultType value() {
static MaybeConst<ValueType> x{ Value };
if constexpr (ref == rvalue)
return std::move(x);
else
return x;
}

struct X {
ResultType member = 0;
ValueType member{ 0 };
};
//! Structure can specialize with only a non-const member function
struct Y {
ResultType memberfunction() { return 1; }
template<bool C> auto memberfunction()
-> std::enable_if_t<!C, ResultType> { return value<1>(); }
template<bool C> auto memberfunction() const
-> std::enable_if_t<C, ResultType> { return value<1>(); }
};
//! Structure always with a const member function
struct Z {
ResultType constmemberfunction() const { return 2; }
ResultType constmemberfunction() const { return value<2>(); }
};
ResultType nakedFunction(float) { return 3; }
auto moveOnly() {
return [u = std::make_unique<X>()](double){ return 4; };
static ResultType nakedFunction(float) { return value<3>(); }
static auto moveOnly() {
// Note: remove trailing return type and compilation of Visit correctly
// breaks because a value, not reference results for one alternative
return [u = std::make_unique<X>()](double) -> ResultType
{ return value<4>(); };
}
struct CopyOnly{
CopyOnly() = default;
CopyOnly(const CopyOnly&) = default;
CopyOnly(CopyOnly&&) = delete;

ResultType operator() (long double) const & { return 5; }
ResultType operator() (long double) const && { return 6; }
ResultType operator() (long double) & { return 7; }
ResultType operator() (long double) && { return 8; }
ResultType operator() (long double) const & { return value<5>(); }
ResultType operator() (long double) const && { return value<6>(); }
ResultType operator() (long double) & { return value<7>(); }
ResultType operator() (long double) && { return value<8>(); }
};

// INVOKE-ing pointer to member always gives lvalue references.
// So if &X::member below is one of the captured invocables,
// and the given variant can contain X,
// then the visitor returns an lvalue reference for the X alternative,
// therefore it must also return a reference for all alternatives.
using VariantType = std::conditional_t<ref == lvalue,
std::variant<
MaybeConst<X>,
MaybeConst<Y>,
MaybeConst<Z>,
MaybeConst<float>,
MaybeConst<double>,
MaybeConst<long double>
>,
std::variant<
// omit X
MaybeConst<Y>,
MaybeConst<Z>,
MaybeConst<float>,
MaybeConst<double>,
MaybeConst<long double>
>
>;

template<typename Visitor, typename Arg>
static void testCase(const Visitor &visitor, int result, Arg &arg)
{
if constexpr(Const)
{
const std::remove_reference_t<decltype(arg)> carg{ arg };
const VariantType cv{ carg };
REQUIRE(result == visitor(carg));
REQUIRE(result == Visit(visitor, cv));
if constexpr (ref != lvalue) {
REQUIRE(result == visitor(std::move(carg)));
REQUIRE(result == Visit(visitor, move(cv)));
}
}

{
VariantType v{ arg };
REQUIRE(result == visitor(arg));
REQUIRE(result == Visit(visitor, v));
if constexpr (ref != lvalue) {
REQUIRE(result == visitor(std::move(arg)));
REQUIRE(result == Visit(visitor, move(v)));
}
}
};
}

TEST_CASE("Variant", "")
void DoTests()
{
// Variant::Visitor can capture many kinds of things. Test each.
// This also tests compilation of the variadic constructor of visitor
// which can take a mix of l- and rvalues.
CopyOnly copyOnly;
const auto visitor = Variant::Visitor(
const auto visitor = Visitor(
&X::member,
&Y::memberfunction,
&Y::template memberfunction<Const>,
&Z::constmemberfunction,
nakedFunction,
moveOnly(),
copyOnly
);

// Verify the right result,
// and verify it compiles for two kinds of references
auto nonConstTestCase = [&](int result, auto &arg){
REQUIRE(result == visitor(arg));
REQUIRE(result == visitor(std::move(arg)));
};
// Verify the right result,
// and verify it compiles for four kinds of references
auto completeTestCast = [&](int result, auto &arg){
const std::remove_reference_t<decltype(arg)> carg{ arg };
nonConstTestCase(result, arg);
REQUIRE(result == visitor(carg));
REQUIRE(result == visitor(std::move(carg)));
};

X x;
Y y;
Z z;
float f{};
double d{};
long double ld{};

completeTestCast(0, x);
nonConstTestCase(1, y); // Correctly fails to compile the complete case
completeTestCast(2, z);
completeTestCast(3, f);
completeTestCast(4, d);
completeTestCast(5, ld);

// The invocable distinguishes its own value category and constness

// Pointer to data member, captured as INVOKE-able, can only return lvalue
// references, so it is excluded from other test cases
if constexpr (ref == lvalue)
testCase(visitor, 0, x);

testCase(visitor, 1, y);
testCase(visitor, 2, z);
testCase(visitor, 3, f);
testCase(visitor, 4, d);
testCase(visitor, 5, ld);

// An invocable can distinguish its own value category and constness
REQUIRE(6 == std::move(visitor)(ld));
using ConstVisitorType = decltype(visitor);
using VisitorType = std::remove_const_t<ConstVisitorType>;
auto &mutVisitor = const_cast<VisitorType&>(visitor);
REQUIRE(7 == mutVisitor(ld));
REQUIRE(8 == std::move(mutVisitor)(ld));
}

}; // stateless struct Tester

TEST_CASE("Variant visitors returning T &")
{
Tester<false>{}.DoTests();
}

TEST_CASE("Variant visitors returning T const &")
{
Tester<true>{}.DoTests();
}

TEST_CASE("Variant visitors returning T &&")
{
Tester<false, rvalue>{}.DoTests();
}

TEST_CASE("Variant visitors returning T const &&")
{
Tester<true, rvalue>{}.DoTests();
}

TEST_CASE("Variant visitors returning T")
{
Tester<false, noref>{}.DoTests();
}

TEST_CASE("Variant visitors returning T const")
{
Tester<true, noref>{}.DoTests();
}

0 comments on commit ce02e4f

Please sign in to comment.