Skip to content
Snippets Groups Projects
Commit c2633503 authored by Ben Cumming's avatar Ben Cumming Committed by Sam Yates
Browse files

Implement `util::any`

Partial implementation of `std::any` from C++17 standard. See: http://en.cppreference.com/w/cpp/utility/any

The implementation is in the `util` library as `util::any`.

Deviations from the standards description of `std::any`:
  * Does not avoid dynamic allocation of small objects.
  * Does not implement the `in_place_type<T>` constructors from the standard.
  * Does not implement the `emplace` modifier from the standard.
parent d0b6b475
No related branches found
No related tags found
No related merge requests found
#pragma once
#include <memory>
#include <typeinfo>
#include <type_traits>
#include <util/meta.hpp>
// Partial implementation of std::any from C++17 standard.
// http://en.cppreference.com/w/cpp/utility/any
//
// Implements a standard-compliant subset of the full interface.
//
// - Does not avoid dynamic allocation of small objects.
// - Does not implement the in_place_type<T> constructors from the standard.
// - Does not implement the emplace modifier from the standard.
namespace nest {
namespace mc {
namespace util {
// Defines a type of object to be thrown by the value-returning forms of
// util::any_cast on failure.
// http://en.cppreference.com/w/cpp/utility/any/bad_any_cast
class bad_any_cast: public std::bad_cast {
public:
const char* what() const noexcept override {
return "bad any cast";
}
};
class any {
public:
constexpr any() = default;
any(const any& other): state_(other.state_->copy()) {}
any(any&& other) noexcept {
std::swap(other.state_, state_);
}
template <
typename T,
typename = typename util::enable_if_t<!std::is_same<util::decay_t<T>, any>::value>
>
any(T&& other) {
using contained_type = util::decay_t<T>;
static_assert(std::is_copy_constructible<contained_type>::value,
"Type of contained object stored in any must satisfy the CopyConstructible requirements.");
state_.reset(new model<contained_type>(std::forward<T>(other)));
}
any& operator=(const any& other) {
state_.reset(other.state_->copy());
return *this;
}
any& operator=(any&& other) noexcept {
swap(other);
return *this;
}
template <
typename T,
typename = typename util::enable_if_t<!std::is_same<util::decay_t<T>, any>::value>
>
any& operator=(T&& other) {
using contained_type = util::decay_t<T>;
static_assert(std::is_copy_constructible<contained_type>::value,
"Type of contained object stored in any must satisfy the CopyConstructible requirements.");
state_.reset(new model<contained_type>(std::forward<T>(other)));
return *this;
}
void reset() noexcept {
state_.reset(nullptr);
}
void swap(any& other) noexcept {
std::swap(other.state_, state_);
}
bool has_value() const noexcept {
return (bool)state_;
}
const std::type_info& type() const noexcept {
return has_value()? state_->type(): typeid(void);
}
private:
struct interface {
virtual ~interface() = default;
virtual const std::type_info& type() = 0;
virtual interface* copy() = 0;
virtual void* pointer() = 0;
virtual const void* pointer() const = 0;
};
template <typename T>
struct model: public interface {
~model() = default;
model(const T& other): value(other) {}
model(T&& other): value(std::move(other)) {}
interface* copy() override {
return new model<T>(*this);
}
const std::type_info& type() override {
return typeid(T);
}
void* pointer() override {
return &value;
}
const void* pointer() const override {
return &value;
}
T value;
};
std::unique_ptr<interface> state_;
protected:
template <typename T>
friend const T* any_cast(const any* operand);
template <typename T>
friend T* any_cast(any* operand);
template <typename T>
T* unsafe_cast() {
return static_cast<T*>(state_->pointer());
}
template <typename T>
const T* unsafe_cast() const {
return static_cast<const T*>(state_->pointer());
}
};
namespace impl {
template <typename T>
using any_cast_remove_qual = typename
std::remove_cv<typename std::remove_reference<T>::type>::type;
} // namespace impl
// If operand is not a null pointer, and the typeid of the requested T matches
// that of the contents of operand, a pointer to the value contained by operand,
// otherwise a null pointer.
template<class T>
const T* any_cast(const any* operand) {
if (operand && operand->type()==typeid(T)) {
return operand->unsafe_cast<T>();
}
return nullptr;
}
template<class T>
T* any_cast(any* operand) {
if (operand && operand->type()==typeid(T)) {
return operand->unsafe_cast<T>();
}
return nullptr;
}
template<class T>
T any_cast(const any& operand) {
using U = impl::any_cast_remove_qual<T>;
static_assert(std::is_constructible<T, const U&>::value,
"any_cast type can't construct copy of contained object");
auto ptr = any_cast<U>(&operand);
if (ptr==nullptr) {
throw bad_any_cast();
}
return static_cast<T>(*ptr);
}
template<class T>
T any_cast(any& operand) {
using U = impl::any_cast_remove_qual<T>;
static_assert(std::is_constructible<T, U&>::value,
"any_cast type can't construct copy of contained object");
auto ptr = any_cast<U>(&operand);
if (ptr==nullptr) {
throw bad_any_cast();
}
return static_cast<T>(*ptr);
}
template<class T>
T any_cast(any&& operand) {
using U = impl::any_cast_remove_qual<T>;
static_assert(std::is_constructible<T, U>::value,
"any_cast type can't construct copy of contained object");
auto ptr = any_cast<U>(&operand);
if (ptr==nullptr) {
throw bad_any_cast();
}
return static_cast<T>(std::move(*ptr));
}
// Constructs an any object containing an object of type T, passing the
// provided arguments to T's constructor.
//
// This does not exactly follow the standard, which states that
// make_any is equivalent to
// return std::any(std::in_place_type<T>, std::forward<Args>(args)...);
// i.e. that the contained object should be constructed in place, whereas
// this implementation constructs the object, then moves it into the
// contained object.
// FIXME: rewrite with in_place_type when available.
template <class T, class... Args>
any make_any(Args&&... args) {
return any(T(std::forward<Args>(args) ...));
}
} // namespace util
} // namespace mc
} // namespace nest
...@@ -13,15 +13,15 @@ using namespace nest::mc; ...@@ -13,15 +13,15 @@ using namespace nest::mc;
using communicator_type = communication::communicator<communication::global_policy>; using communicator_type = communication::communicator<communication::global_policy>;
static bool is_dry_run() { inline bool is_dry_run() {
return communication::global_policy::kind() == return communication::global_policy::kind() ==
communication::global_policy_kind::dryrun; communication::global_policy_kind::dryrun;
} }
TEST(domain_decomp, basic) { TEST(domain_decomp, basic) {
/*
using policy = communication::global_policy; using policy = communication::global_policy;
/*
const auto num_domains = policy::size(); const auto num_domains = policy::size();
const auto rank = policy::id(); const auto rank = policy::id();
*/ */
......
...@@ -31,6 +31,7 @@ set(TEST_CUDA_SOURCES ...@@ -31,6 +31,7 @@ set(TEST_CUDA_SOURCES
set(TEST_SOURCES set(TEST_SOURCES
# unit tests # unit tests
test_algorithms.cpp test_algorithms.cpp
test_any.cpp
test_backend.cpp test_backend.cpp
test_double_buffer.cpp test_double_buffer.cpp
test_cell.cpp test_cell.cpp
......
#include "../gtest.h"
#include "common.hpp"
#include <iostream>
#include <util/any.hpp>
using namespace nest::mc;
TEST(any, copy_construction) {
util::any any_int(2);
EXPECT_EQ(any_int.type(), typeid(int));
util::any any_float(2.0f);
EXPECT_EQ(any_float.type(), typeid(float));
std::string str = "hello";
util::any any_string(str);
EXPECT_EQ(any_string.type(), typeid(std::string));
}
namespace {
struct moveable {
moveable() = default;
moveable(moveable&& other):
moves(other.moves+1), copies(other.copies)
{}
moveable(const moveable& other):
moves(other.moves), copies(other.copies+1)
{}
int moves=0;
int copies=0;
};
}
TEST(any, move_construction) {
moveable m;
util::any copied(m);
util::any moved(std::move(m));
// Check that the expected number of copies and moves were performed.
// Note that any_cast(any*) is used instead of any_cast(const any&) because
// any_cast(const any&) returns a copy.
const auto& cref = *util::any_cast<moveable>(&copied);
EXPECT_EQ(cref.moves, 0);
EXPECT_EQ(cref.copies, 1);
const auto& mref = *util::any_cast<moveable>(&moved);
EXPECT_EQ(mref.moves, 1);
EXPECT_EQ(mref.copies, 0);
// construction by any&& should not make any copies or moves of the
// constructed value
util::any fin(std::move(moved));
EXPECT_FALSE(moved.has_value()); // moved has been moved from and should be empty
const auto& fref = *util::any_cast<moveable>(&fin);
EXPECT_EQ(fref.moves, 1);
EXPECT_EQ(fref.copies, 0);
const auto value = util::any_cast<moveable>(fin);
EXPECT_EQ(value.moves, 1);
EXPECT_EQ(value.copies, 1);
}
TEST(any, type) {
using util::any;
any anyi(42);
any anys(std::string("hello"));
any anyv(std::vector<int>{1, 2, 3});
any any0;
EXPECT_EQ(typeid(int), anyi.type());
EXPECT_EQ(typeid(std::string), anys.type());
EXPECT_EQ(typeid(std::vector<int>), anyv.type());
EXPECT_EQ(typeid(void), any0.type());
anyi.reset();
EXPECT_EQ(typeid(void), anyi.type());
anyi = std::true_type();
EXPECT_EQ(typeid(std::true_type), anyi.type());
}
TEST(any, swap) {
using util::any;
using util::any_cast;
any any1(42); // integer
any any2(3.14); // double
EXPECT_EQ(typeid(int), any1.type());
EXPECT_EQ(typeid(double), any2.type());
any1.swap(any2);
EXPECT_EQ(any_cast<int>(any2), 42);
EXPECT_EQ(any_cast<double>(any1), 3.14);
EXPECT_EQ(typeid(double), any1.type());
EXPECT_EQ(typeid(int), any2.type());
any1.swap(any2);
EXPECT_EQ(any_cast<double>(any2), 3.14);
EXPECT_EQ(any_cast<int>(any1), 42);
EXPECT_EQ(typeid(int), any1.type());
EXPECT_EQ(typeid(double), any2.type());
}
TEST(any, constness) {
}
// These should fail at compile time if the constraint that the contents of any
// satisfy CopyConstructable. This implementation is rock solid, so they have
// to be commented out.
/*
TEST(any, not_copy_constructable) {
util::any a(testing::nocopy<int>(3));
testing::nocopy<int> x(3);
util::any b(std::move(x));
}
*/
// test any_cast(any*)
// - these have different behavior to any_cast on reference types
// - are used by the any_cast on refernce types
TEST(any, any_cast_ptr) {
// test that valid pointers are returned for int and std::string types
util::any ai(42);
auto ptr_i = util::any_cast<int>(&ai);
EXPECT_EQ(*ptr_i, 42);
util::any as(std::string("hello"));
auto ptr_s = util::any_cast<std::string>(&as);
EXPECT_EQ(*ptr_s, "hello");
// test that exceptions are thrown for invalid casts
EXPECT_EQ(util::any_cast<int>(&as), nullptr);
EXPECT_EQ(util::any_cast<std::string>(&ai), nullptr);
util::any empty;
EXPECT_EQ(util::any_cast<int>(&empty), nullptr);
EXPECT_EQ(util::any_cast<int>((util::any*)nullptr), nullptr);
// Check that constness of the returned pointer matches that the input.
{
util::any a(42);
auto p = util::any_cast<int>(&a);
static_assert(std::is_same<int*, decltype(p)>::value,
"any_cast(any*) should not return const*");
}
{
const util::any a(42);
auto p = util::any_cast<int>(&a);
static_assert(std::is_same<const int*, decltype(p)>::value,
"any_cast(const any*) should return const*");
}
}
TEST(any, any_cast_ref) {
util::any ai(42);
auto i = util::any_cast<int>(ai);
EXPECT_EQ(typeid(i), typeid(int));
EXPECT_EQ(i, 42);
}
// test any_cast(any&&)
TEST(any, any_cast_rvalue) {
auto moved = util::any_cast<moveable>(util::any(moveable()));
EXPECT_EQ(moved.moves, 2);
EXPECT_EQ(moved.copies, 0);
}
TEST(any, std_swap) {
util::any a1(42);
util::any a2(3.14);
auto pi = util::any_cast<int>(&a1);
auto pd = util::any_cast<double>(&a2);
std::swap(a1, a2);
// test that values were swapped
EXPECT_EQ(util::any_cast<int>(a2), 42);
EXPECT_EQ(util::any_cast<double>(a1), 3.14);
// test that underlying pointers did not change
EXPECT_EQ(pi, util::any_cast<int>(&a2));
EXPECT_EQ(pd, util::any_cast<double>(&a1));
}
// test operator=(const any&)
TEST(any, assignment_from_lvalue) {
using std::string;
auto str1 = string("one");
auto str2 = string("two");
util::any a(str1);
util::any b;
b = a; // copy assignment
// verify that b contains value stored in a
EXPECT_EQ(str1, util::any_cast<string>(b));
// change the value stored in b
*util::any_cast<string>(&b) = str2;
// verify that a is unchanged and that b holds new value
EXPECT_EQ(str1, util::any_cast<string>(a));
EXPECT_EQ(str2, util::any_cast<string>(b));
}
// test operator=(any&&)
TEST(any, assignment_from_rvalue) {
using std::string;
auto str1 = string("one");
auto str2 = string("two");
util::any a(str1);
util::any b;
b = std::move(a); // move assignment
EXPECT_EQ(str1, util::any_cast<string>(b));
EXPECT_EQ(nullptr, util::any_cast<string>(&a));
}
// test template<typename T> operator=(T&&)
TEST(any, assignment_from_value) {
std::vector<int> tmp{1, 2, 3};
// take a pointer to the orignal data to later verify
// that the value was moved, and not copied.
auto ptr = tmp.data();
util::any a;
a = std::move(tmp);
auto vec = util::any_cast<std::vector<int>>(&a);
// ensure the value was moved
EXPECT_EQ(ptr, vec->data());
// ensure that the contents of the vector are unchanged
std::vector<int> ref{1, 2, 3};
EXPECT_EQ(ref, *vec);
}
TEST(any, make_any) {
using util::make_any;
using util::any_cast;
{
auto a = make_any<int>(42);
EXPECT_EQ(typeid(int), a.type());
EXPECT_EQ(42, any_cast<int>(a));
}
// check casting
{
auto a = make_any<double>(42u);
EXPECT_EQ(typeid(double), a.type());
EXPECT_EQ(42.0, any_cast<double>(a));
}
// check forwarding of parameters to constructor
{
// create a string from const char*
auto a = make_any<std::string>("hello");
EXPECT_EQ(any_cast<std::string>(a), std::string("hello"));
}
// test that we make_any correctly forwards rvalue arguments to the constructor
// of the contained object.
{
std::vector<int> tmp{1, 2, 3};
// take a pointer to the orignal data to later verify
// that the value was moved, and not copied.
auto ptr = tmp.data();
auto a = make_any<std::vector<int>>(std::move(tmp));
auto vec = any_cast<std::vector<int>>(&a);
// ensure the value was moved
EXPECT_EQ(ptr, vec->data());
// ensure that the contents of the vector are unchanged
std::vector<int> ref{1, 2, 3};
EXPECT_EQ(ref, *vec);
}
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment