blob: 5ab249cd13b0f281d41c07914ed0f0f5247724cd [file] [log] [blame]
// Copyright 2014 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This is an implementation of a "true" variant class in C++.
// The buffet::Any class can hold any C++ type, but both the setter and
// getter sites need to know the actual type of data.
// Note that C-style arrays when stored in Any are reduced to simple
// data pointers. Any will not copy a contents of the array.
// const int data[] = [1,2,3];
// Any v(data); // stores const int*, effectively "Any v(&data[0]);"
// buffet::Any is a value type. Which means, the data is copied into it
// and Any owns it. The owned object (stored by value) will be destroyed
// when Any is cleared or reassigned. The contained value type must be
// copy-constructible. You can also store pointers and references to objects.
// Storing pointers is trivial. In order to store a reference, you can
// use helper functions std::ref() and std::cref() to create non-const and
// const references respectively. In such a case, the type of contained data
// will be std::reference_wrapper<T>. See 'References' unit tests in
// any_unittest.cc for examples.
#ifndef BUFFET_ANY_H_
#define BUFFET_ANY_H_
#include "buffet/any_internal_impl.h"
#include <algorithm>
namespace buffet {
class Any final {
public:
Any() = default;
// Standard copy constructor. This is a value-class container
// that must be copy-constructible and movable. The copy constructors
// should not be marked as explicit.
Any(const Any& rhs);
// Typed constructor that stores a value of type T in the Any.
template<class T>
Any(T value) { // NOLINT(runtime/explicit)
data_buffer_.Assign(std::move(value));
}
// Not declaring the destructor as virtual since this is a sealed class
// and there is no need to introduce a virtual table to it.
~Any();
// Assignment operators.
Any& operator=(const Any& rhs);
template<class T>
Any& operator=(T value) {
data_buffer_.Assign(std::move(value));
return *this;
}
// Checks if the given type DestType can be obtained from the Any.
// For example, to check if Any has a 'double' value in it:
// any.IsTypeCompatible<double>()
template<typename DestType>
bool IsTypeCompatible() const {
// Make sure the requested type DestType conforms to the storage
// requirements of Any. We always store the data by value, which means we
// strip away any references as well as cv-qualifiers. So, if the user
// stores "const int&", we actually store just an "int".
// When calling IsTypeCompatible, we need to do a similar "type cleansing"
// to make sure the requested type matches the type of data actually stored,
// so this "canonical" type is used for type checking below.
using CanonicalDestType = typename std::decay<DestType>::type;
const std::type_info& ContainedTypeId = GetType();
if (typeid(CanonicalDestType) == ContainedTypeId)
return true;
if (!std::is_pointer<CanonicalDestType>::value)
return false;
// If asking for a const pointer from a variant containing non-const
// pointer, still satisfy the request. So, we need to remove the pointer
// specification first, then strip the const/volatile qualifiers, then
// re-add the pointer back, so "const int*" would become "int*".
using NonPointer = typename std::remove_pointer<CanonicalDestType>::type;
using CanonicalDestTypeNoConst = typename std::add_pointer<
typename std::remove_const<NonPointer>::type>::type;
using CanonicalDestTypeNoVolatile = typename std::add_pointer<
typename std::remove_volatile<NonPointer>::type>::type;
using CanonicalDestTypeNoConstOrVolatile = typename std::add_pointer<
typename std::remove_cv<NonPointer>::type>::type;
return typeid(CanonicalDestTypeNoConst) == ContainedTypeId ||
typeid(CanonicalDestTypeNoVolatile) == ContainedTypeId ||
typeid(CanonicalDestTypeNoConstOrVolatile) == ContainedTypeId;
}
// Returns immutable data contained in Any.
// Aborts if Any doesn't contain a value of type T, or trivially
// convertible to/compatible with it.
template<typename T>
const T& Get() const {
CHECK(IsTypeCompatible<T>()) << "Requesting value of type "
<< typeid(T).name()
<< " from variant containing "
<< GetType().name();
return data_buffer_.GetData<T>();
}
// Returns a pointer to mutable value of type T contained within Any.
// No data copying is made, the data pointed to is still owned by Any.
// If Any doesn't contain a value of type T, or trivially
// convertible/compatible to/with it, then it returns nullptr.
template<typename T>
T* GetPtr() {
if (!IsTypeCompatible<T>())
return nullptr;
return &(data_buffer_.GetData<T>());
}
// Returns immutable data contained in Any.
// If the Any doesn't contain a compatible value, the provided default
// |def_val| is returned instead.
template<typename T>
const T& TryGet(typename std::decay<T>::type const& def_val) const {
if (!IsTypeCompatible<T>())
return def_val;
return data_buffer_.GetData<T>();
}
// A convenience specialization of the above function where the default
// value of type T is returned in case the underlying Get() fails.
template<typename T>
const T& TryGet() const {
return TryGet<T>(typename std::decay<T>::type());
}
// Returns the type information about the contained data. For most cases,
// instead of using this function, you should be calling IsTypeCompatible<>().
const std::type_info& GetType() const;
// Swaps the value of this object with that of |other|.
void Swap(Any& other);
// Checks if Any is empty, that is, not containing a value of any type.
bool IsEmpty() const;
// Clears the Any and destroys any contained object. Makes it empty.
void Clear();
// Checks if Any contains a type convertible to integer.
// Any type that match std::is_integral<T> and std::is_enum<T> is accepted.
// That includes signed and unsigned char, short, int, long, etc as well as
// 'bool' and enumerated types.
// For 'integer' type, you can call GetAsInteger to do implicit type
// conversion to intmax_t.
bool IsConvertibleToInteger() const;
// For integral types and enums contained in the Any, get the integer value
// of data. This is a useful function to obtain an integer value when
// any can possibly have unspecified integer, such as 'short', 'unsigned long'
// and so on.
intmax_t GetAsInteger() const;
private:
// The data buffer for contained object.
internal_details::Buffer data_buffer_;
};
} // namespace buffet
namespace std {
// Specialize std::swap() algorithm for buffet::Any class.
inline void swap(buffet::Any& lhs, buffet::Any& rhs) {
lhs.Swap(rhs);
}
} // namespace std
#endif // BUFFET_ANY_H_