This commit is contained in:
Bartosz Wieczorek 2018-03-12 07:45:01 +01:00
parent 451b0bbebb
commit 3dc4d99722
66 changed files with 303 additions and 11240 deletions

View File

@ -1,20 +0,0 @@
cmake_minimum_required(VERSION 3.0.2)
project(Benchmarks LANGUAGES CXX)
include_directories( . )
include_directories( ${eedb_app_SOURCE_DIR} )
include_directories( ../share )
include_directories( ${benchmarks_SOURCE_DIR}/include)
set(TEST_EXECUTABLE_NAME eedb_benchmarks )
#add test files
file(GLOB_RECURSE TEST_FILES *.cpp )
INCLUDE_DIRECTORIES(${PostgreSQL_INCLUDE_DIRS})
add_executable( ${TEST_EXECUTABLE_NAME} ${TEST_FILES})
target_link_libraries( ${TEST_EXECUTABLE_NAME} benchmark wt wttest eedb_db auth ${Boost_LIBRARIES} )
add_test( ${TEST_EXECUTABLE_NAME} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TEST_EXECUTABLE_NAME})

View File

@ -1,62 +0,0 @@
//#include <benchmark/benchmark.h>
//#include <eedb/widgets/AuthWidget.hpp>
//class AuthWithDifferentSignals final : public eedb::AuthPage {
// // AuthPage interface
// public:
// void setParent(Wt::WContainerWidget * parent) override {}
// void detachFrom(Wt::WContainerWidget * parent) override {}
// void processEnvironment() override {}
// void registerNeedVerification(std::function< void() > f) override {
// _callbacks[0] = f;
// }
// void registerOnUserWeakLogin(std::function< void() > f) override {
// _callbacks[1] = f;
// }
// void registerOnUserStrongLogin(std::function< void() > f) override {
// _callbacks[2] = f;
// }
// void registerOnUserLogout(std::function< void() > f) override {
// _callbacks[3] = f;
// }
// // protected:
// void notifyUserStrongLogin() const override {
// _callbacks[0]();
// }
// void notifyUserWeakLogin() const override {
// _callbacks[1]();
// }
// void notifyNeedEmailVerification() const override {
// _callbacks[0]();
// }
// void notifyUserLogout() const override {
// _callbacks[3]();
// }
// private:
// std::array< std::function< void() >, 4 > _callbacks;
//};
//#include <Wt/Auth/Login>
//static void BoostSignals(benchmark::State & state) {
// eedb::AuthPageImpl sut;
// auto callback = []() {};
// sut.registerNeedVerification(callback);
// while(state.KeepRunning()) {
// sut.notifyNeedEmailVerification();
// }
//}
//static void TableBasedSignals(benchmark::State & state) {
// AuthWithDifferentSignals sut;
// auto callback = []() {};
// sut.registerNeedVerification(callback);
// while(state.KeepRunning()) {
// sut.notifyNeedEmailVerification();
// }
//}
//BENCHMARK(BoostSignals);
//BENCHMARK(TableBasedSignals);

View File

@ -1,3 +0,0 @@
#include <benchmark/benchmark.h>
BENCHMARK_MAIN()

View File

@ -1,16 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_HPP
#define DYNO_HPP
#include <dyno/builtin.hpp>
#include <dyno/concept.hpp>
#include <dyno/concept_map.hpp>
#include <dyno/macro.hpp>
#include <dyno/poly.hpp>
#include <dyno/storage.hpp>
#include <dyno/vtable.hpp>
#endif // DYNO_HPP

View File

@ -1,134 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_BUILTIN_HPP
#define DYNO_BUILTIN_HPP
#include <dyno/concept.hpp>
#include <dyno/concept_map.hpp>
#include <cstddef>
#include <type_traits>
#include <typeinfo>
namespace dyno {
// Encapsulates the minimal amount of information required to allocate
// storage for an object of a given type.
//
// This should never be created explicitly; always use `dyno::storage_info_for`.
struct storage_info {
std::size_t size;
std::size_t alignment;
};
template <typename T>
constexpr auto storage_info_for = storage_info{sizeof(T), alignof(T)};
struct Storable : decltype(dyno::requires(
"storage_info"_s = dyno::function<dyno::storage_info()>
)) { };
template <typename T>
auto const default_concept_map<Storable, T> = dyno::make_concept_map(
"storage_info"_s = []() { return dyno::storage_info_for<T>; }
);
struct TypeId : decltype(dyno::requires(
"typeid"_s = dyno::function<std::type_info const&()>
)) { };
template <typename T>
auto const default_concept_map<TypeId, T> = dyno::make_concept_map(
"typeid"_s = []() -> std::type_info const& { return typeid(T); }
);
struct DefaultConstructible : decltype(dyno::requires(
"default-construct"_s = dyno::function<void (void*)>
)) { };
template <typename T>
auto const default_concept_map<DefaultConstructible, T,
std::enable_if_t<std::is_default_constructible<T>::value>
> = dyno::make_concept_map(
"default-construct"_s = [](void* p) {
new (p) T();
}
);
struct MoveConstructible : decltype(dyno::requires(
"move-construct"_s = dyno::function<void (void*, dyno::T&&)>
)) { };
template <typename T>
auto const default_concept_map<MoveConstructible, T,
std::enable_if_t<std::is_move_constructible<T>::value>
> = dyno::make_concept_map(
"move-construct"_s = [](void* p, T&& other) {
new (p) T(std::move(other));
}
);
struct CopyConstructible : decltype(dyno::requires(
dyno::MoveConstructible{},
"copy-construct"_s = dyno::function<void (void*, dyno::T const&)>
)) { };
template <typename T>
auto const default_concept_map<CopyConstructible, T,
std::enable_if_t<std::is_copy_constructible<T>::value>
> = dyno::make_concept_map(
"copy-construct"_s = [](void* p, T const& other) {
new (p) T(other);
}
);
struct MoveAssignable : decltype(dyno::requires(
// No virtual function required to support this so far
)) { };
struct CopyAssignable : decltype(dyno::requires(
dyno::MoveAssignable{}
// No additional virtual functions required to support this so far
)) { };
struct Swappable : decltype(dyno::requires(
// No virtual functions required to support this so far
)) { };
struct EqualityComparable : decltype(dyno::requires(
"equal"_s = dyno::function<bool (dyno::T const&, dyno::T const&)>
)) { };
template <typename T>
auto const default_concept_map<EqualityComparable, T,
decltype((void)(std::declval<T>() == std::declval<T>()))
> = dyno::make_concept_map(
"equal"_s = [](T const& a, T const& b) -> bool { return a == b; }
);
struct Destructible : decltype(dyno::requires(
"destruct"_s = dyno::function<void (dyno::T&)>
)) { };
template <typename T>
auto const default_concept_map<Destructible, T,
std::enable_if_t<std::is_destructible<T>::value>
> = dyno::make_concept_map(
"destruct"_s = [](T& self) { self.~T(); }
);
} // end namespace dyno
#endif // DYNO_BUILTIN_HPP

View File

@ -1,162 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_CONCEPT_HPP
#define DYNO_CONCEPT_HPP
#include <dyno/detail/dsl.hpp>
#include <dyno/detail/has_duplicates.hpp>
#include <boost/hana/any_of.hpp>
#include <boost/hana/at_key.hpp>
#include <boost/hana/basic_tuple.hpp>
#include <boost/hana/bool.hpp>
#include <boost/hana/contains.hpp>
#include <boost/hana/core/to.hpp>
#include <boost/hana/filter.hpp>
#include <boost/hana/first.hpp>
#include <boost/hana/flatten.hpp>
#include <boost/hana/map.hpp>
#include <boost/hana/pair.hpp>
#include <boost/hana/transform.hpp>
#include <type_traits>
#include <utility>
namespace dyno {
template <typename ...Clauses>
struct concept;
namespace detail {
template <typename Str, typename Fun>
constexpr boost::hana::basic_tuple<boost::hana::pair<Str, Fun>>
expand_clauses(boost::hana::pair<Str, Fun> const&)
{ return {}; }
template <typename ...Clauses>
constexpr auto expand_clauses(dyno::concept<Clauses...> const&) {
return boost::hana::flatten(
boost::hana::make_basic_tuple(detail::expand_clauses(Clauses{})...)
);
}
struct concept_base { };
} // end namespace detail
// Returns a sequence containing all the clauses of the given concept and
// its derived concepts.
//
// In the returned sequence, each clause is a pair where the first element
// is the name of the clause and the second element is the clause itself
// (e.g. a `dyno::function`). The order of clauses is not specified.
template <typename ...Clauses>
constexpr auto clauses(dyno::concept<Clauses...> const&) {
auto all = boost::hana::make_basic_tuple(detail::expand_clauses(Clauses{})...);
return boost::hana::flatten(all);
}
// Returns a sequence containing the names associated to all the claused of
// the given concept, and its derived concepts.
//
// The order of the clause names is not specified.
template <typename ...Clauses>
constexpr auto clause_names(dyno::concept<Clauses...> const& c) {
return boost::hana::transform(dyno::clauses(c), boost::hana::first);
}
// Returns a sequence of the concepts refined by the given concept.
//
// Only the concepts that are refined directly by `c` are returned, i.e. we
// do not get the refined concepts of the refined concepts recursively.
template <typename ...Clauses>
constexpr auto refined_concepts(dyno::concept<Clauses...> const&) {
return boost::hana::filter(boost::hana::make_basic_tuple(Clauses{}...), [](auto t) {
constexpr bool IsBase = std::is_base_of<detail::concept_base, decltype(t)>::value;
return boost::hana::bool_c<IsBase>;
});
}
namespace detail {
template <typename ...Clauses>
constexpr auto direct_clauses(dyno::concept<Clauses...> const&) {
return boost::hana::filter(boost::hana::make_basic_tuple(Clauses{}...), [](auto t) {
constexpr bool IsBase = std::is_base_of<detail::concept_base, decltype(t)>::value;
return boost::hana::bool_c<!IsBase>;
});
}
template <typename ...Clauses>
constexpr auto has_duplicate_clause(dyno::concept<Clauses...> const& c) {
auto direct = detail::direct_clauses(c);
return detail::has_duplicates(boost::hana::transform(direct, boost::hana::first));
}
template <typename ...Clauses>
constexpr auto is_redefining_base_concept_clause(dyno::concept<Clauses...> const& c) {
auto bases = dyno::refined_concepts(c);
auto base_clause_names = boost::hana::unpack(bases, [](auto ...bases) {
auto all = boost::hana::flatten(boost::hana::make_basic_tuple(dyno::clauses(bases)...));
return boost::hana::transform(all, boost::hana::first);
});
return boost::hana::any_of(detail::direct_clauses(c), [=](auto clause) {
return boost::hana::contains(base_clause_names, boost::hana::first(clause));
});
}
} // end namespace detail
// A `concept` is a collection of clauses and refined concepts representing
// requirements for a type to model the concept.
//
// A concept is created by using `dyno::requires`.
//
// From a `concept`, one can generate a virtual function table by looking at
// the signatures of the functions defined in the concept. In the future, it
// would also be possible to do much more, like getting a predicate that checks
// whether a type satisfies the concept.
template <typename ...Clauses>
struct concept : detail::concept_base {
static_assert(!decltype(detail::has_duplicate_clause(std::declval<concept>())){},
"dyno::concept: It looks like you have multiple clauses with the same "
"name in your concept definition. This is not allowed; each clause must "
"have a different name.");
static_assert(!decltype(detail::is_redefining_base_concept_clause(std::declval<concept>())){},
"dyno::concept: It looks like you are redefining a clause that is already "
"defined in a base concept. This is not allowed; clauses defined in a "
"concept must have a distinct name from clauses defined in base concepts "
"if there are any.");
template <typename Name>
constexpr auto get_signature(Name name) const {
auto clauses = boost::hana::to_map(dyno::clauses(*this));
return clauses[name];
}
};
// Creates a `concept` with the given clauses. Note that a clause may be a
// concept itself, in which case the clauses of that concept are used, and
// that, recursively. For example:
//
// ```
// template <typename Reference>
// struct Iterator : decltype(dyno::requires(
// Incrementable{},
// "dereference"_s = dyno::function<Reference (dyno::T&)>
// ...
// )) { };
// ```
//
// It is recommended to make every concept its own structure (and not just an
// alias), as above, because that ensures the uniqueness of concepts that have
// the same clauses.
template <typename ...Clauses>
constexpr dyno::concept<Clauses...> requires(Clauses ...) {
return {};
}
} // end namespace dyno
#endif // DYNO_CONCEPT_HPP

View File

@ -1,285 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_CONCEPT_MAP_HPP
#define DYNO_CONCEPT_MAP_HPP
#include <dyno/concept.hpp>
#include <dyno/detail/bind_signature.hpp>
#include <dyno/detail/dsl.hpp>
#include <dyno/detail/empty_object.hpp>
#include <dyno/detail/has_duplicates.hpp>
#include <boost/hana/at_key.hpp>
#include <boost/hana/bool.hpp>
#include <boost/hana/contains.hpp>
#include <boost/hana/core/to.hpp>
#include <boost/hana/difference.hpp>
#include <boost/hana/fold_left.hpp>
#include <boost/hana/is_subset.hpp>
#include <boost/hana/keys.hpp>
#include <boost/hana/map.hpp>
#include <boost/hana/pair.hpp>
#include <boost/hana/set.hpp>
#include <boost/hana/union.hpp>
#include <boost/hana/unpack.hpp>
#include <type_traits>
#include <utility>
namespace dyno {
namespace detail {
// We wrap all lambdas and function objects passed to the library using this
// hack, so that we can pretend stateless lambdas are default constructible
// in the rest of the library.
template <typename F, typename Signature>
struct default_constructible_lambda;
template <typename F, typename R, typename ...Args>
struct default_constructible_lambda<F, R(Args...)> {
constexpr R operator()(Args ...args) const {
auto lambda = detail::empty_object<F>::get();
return lambda(std::forward<Args>(args)...);
}
};
template <typename F, typename ...Args>
struct default_constructible_lambda<F, void(Args...)> {
constexpr void operator()(Args ...args) const {
auto lambda = detail::empty_object<F>::get();
lambda(std::forward<Args>(args)...);
}
};
} // end namespace detail
// A concept map is a statically-known mapping from functions implemented by
// a type `T` to functions defined by a concept. A concept map is what's being
// used to fill the vtable extracted from a concept definition. An instance of
// this type is never created as-is; `dyno::make_concept_map` must always be used.
//
// Note that everything in the concept map is known statically. Specifically,
// the types of the functions in the concept map are known statically, and
// e.g. lambdas will be stored as-is (not as function pointers).
template <typename Concept, typename T, typename ...Mappings>
struct concept_map_t;
template <typename Concept, typename T, typename ...Name, typename ...Function>
struct concept_map_t<Concept, T, boost::hana::pair<Name, Function>...> {
constexpr concept_map_t() = default;
template <typename Name_>
constexpr auto operator[](Name_ name) const {
constexpr bool is_known_function = boost::hana::contains(as_hana_map{}, name);
if constexpr (is_known_function) {
return boost::hana::at_key(as_hana_map{}, name);
} else {
static_assert(is_known_function,
"dyno::concept_map_t::operator[]: Request for the implementation of a "
"function that was not provided in the concept map. Make sure the "
"concept map contains the proper functions, and that you're requesting "
"the right function from the concept map. You can find the contents of "
"the concept map and the function you were trying to access in the "
"compiler error message, probably in the following format: "
"`concept_map_t<CONCEPT, MODEL, CONTENTS OF CONCEPT MAP>::operator[]<FUNCTION NAME>`");
}
}
private:
using as_hana_map = boost::hana::map<
boost::hana::pair<
Name,
detail::default_constructible_lambda<
Function,
typename detail::bind_signature<
typename decltype(Concept{}.get_signature(Name{}))::type, T
>::type
>
>...
>;
};
// Creates a concept map associating function names to function implementations.
//
// The exact contents of the map must be pairs where the first element is a
// function name (represented as a compile-time string), and the second element
// is the implementation of that function (as a stateless function object).
//
// Note that a concept map created with this function can be incomplete. Before
// being used, it must be completed using `dyno::complete_concept_map`.
template <typename ...Name, typename ...Function>
constexpr auto make_concept_map(boost::hana::pair<Name, Function> ...mappings) {
auto map = boost::hana::make_map(mappings...);
static_assert(!decltype(detail::has_duplicates(boost::hana::keys(map)))::value,
"dyno::make_concept_map: It looks like you have multiple entries with the "
"same name in your concept map. This is not allowed; each entry must have "
"a different name.");
return map;
}
// Customization point for concept writers to provide default models of
// their concepts.
//
// This can be specialized by concept authors to provide a concept map that
// will be used when no custom concept map is specified. The third parameter
// can be used to define a default concept map for a family of type, by using
// `std::enable_if`.
template <typename Concept, typename T, typename = void>
auto const default_concept_map = dyno::make_concept_map();
// Customization point for users to define their models of concepts.
//
// This can be specialized by clients to provide concept maps for the concepts
// and types they wish. The third parameter can be used to define a concept
// map for a family of type, by using `std::enable_if`.
template <typename Concept, typename T, typename = void>
auto const concept_map = dyno::make_concept_map();
namespace detail {
// Takes a Hana map, and completes it by interpreting it as a concept map
// for fulfilling the given `Concept` for the given type `T`.
template <typename Concept, typename T, typename Map>
constexpr auto complete_concept_map_impl(Map map) {
// 1. Bring in the functions provided in the default concept map.
auto with_defaults = boost::hana::union_(dyno::default_concept_map<Concept, T>, map);
// 2. For each refined concept, recursively complete the concept map for
// that Concept and merge that into the current concept map.
auto refined = dyno::refined_concepts(Concept{});
auto merged = boost::hana::fold_left(refined, with_defaults, [](auto m, auto c) {
using C = decltype(c);
auto completed = detail::complete_concept_map_impl<C, T>(dyno::concept_map<C, T>);
return boost::hana::union_(completed, m);
});
return merged;
}
// Turns a Hana map into a concept map.
template <typename Concept, typename T, typename Map>
constexpr auto to_concept_map(Map map) {
return boost::hana::unpack(map, [](auto ...m) {
return dyno::concept_map_t<Concept, T, decltype(m)...>{};
});
}
// Returns whether a Hana map, when interpreted as a concept map for fulfilling
// the given `Concept`, is missing any functions.
template <typename Concept, typename T, typename Map>
struct concept_map_is_complete : decltype(boost::hana::is_subset(
dyno::clause_names(Concept{}),
boost::hana::keys(std::declval<Map>())
)) { };
} // end namespace detail
// Returns whether the type `T` models the given `Concept`.
//
// Specifically, checks whether it is possible to complete the concept map of
// the given type for the given concept. Usage goes as follows:
// ```
// static_assert(dyno::models<Drawable, my_polygon>);
// ```
template <typename Concept, typename T>
constexpr auto models = detail::concept_map_is_complete<
Concept, T,
decltype(detail::complete_concept_map_impl<Concept, T>(
dyno::concept_map<Concept, T>
))
>{};
namespace diagnostic {
template <typename ...> struct ________________THE_CONCEPT_IS;
template <typename ...> struct ________________YOUR_MODEL_IS;
template <typename ...> struct ________________FUNCTIONS_MISSING_FROM_YOUR_CONCEPT_MAP;
template <typename ...> struct ________________FUNCTIONS_DECLARED_IN_YOUR_CONCEPT_MAP;
template <typename ...> struct ________________FUNCTIONS_REQUIRED_BY_THE_CONCEPT;
template <typename ...> struct ________________EXACT_TYPE_OF_YOUR_CONCEPT_MAP;
template <typename ..., bool concept_map_is_complete = false>
constexpr void INCOMPLETE_CONCEPT_MAP() {
static_assert(concept_map_is_complete,
"dyno::concept_map: Incomplete definition of your concept map. Despite "
"looking at the default concept map for this concept and the concept "
"maps for all the concepts this concept refines, I can't find definitions "
"for all the functions that the concept requires. Please make sure you did "
"not forget to define a function in your concept map, and otherwise make "
"sure the proper default concept maps are kicking in. You can find information "
"to help you debug this error in the compiler error message, probably in "
"the instantiation of the INCOMPLETE_CONCEPT_MAP<.....> function. Good luck!");
}
} // end namespace diagnostic
// Turns a Hana map into a fully cooked concept map ready for consumption
// by a vtable.
//
// The concept maps for all the concepts that `Concept` refines are merged with
// the mappings provided explicitly. For example:
// ```
// struct A : decltype(dyno::requires(
// "f"_s = dyno::function<void (dyno::T&)>
// )) { };
//
// struct B : decltype(dyno::requires(
// A{},
// "g"_s = dyno::function<int (dyno::T&)>
// )) { };
//
// struct Foo { };
//
// template <>
// auto const dyno::concept_map<A, Foo> = dyno::make_concept_map(
// "f"_s = [](Foo&) { }
// );
//
// template <>
// auto const dyno::concept_map<B, Foo> = dyno::make_concept_map(
// "g"_s = [](Foo&) { return 0; }
// );
//
// auto complete = dyno::complete_concept_map<B, Foo>(dyno::concept_map<B, Foo>);
// // `f` is automatically pulled from `concept<A, Foo>`
// ```
//
// Furthermore, if the same function is defined in more than one concept map
// in the full refinement tree, it is undefined which one is used. Therefore,
// all of the implementations better be the same! This is easy to enforce by
// never defining a function in a concept map where the concept does not
// require that function.
//
// Despite the above, which relates to functions defined in different concepts
// that have a refinement relation, if a function is provided both in the
// default concept map and in a customized concept map for the same concept,
// the function in the customized map is preferred.
//
// Also, after looking at the whole refinement tree, including the default
// concept maps, it is an error if any function required by the concept can't
// be resolved.
template <typename Concept, typename T, typename Map>
constexpr auto complete_concept_map(Map map) {
auto complete_map = detail::complete_concept_map_impl<Concept, T>(map);
auto as_concept_map = detail::to_concept_map<Concept, T>(complete_map);
constexpr auto is_complete = detail::concept_map_is_complete<Concept, T, decltype(complete_map)>{};
if constexpr (is_complete) {
return as_concept_map;
} else {
auto required = boost::hana::to_set(dyno::clause_names(Concept{}));
auto declared = boost::hana::to_set(boost::hana::keys(complete_map));
auto missing = boost::hana::difference(required, declared);
diagnostic::INCOMPLETE_CONCEPT_MAP<
diagnostic::________________THE_CONCEPT_IS<Concept>,
diagnostic::________________YOUR_MODEL_IS<T>,
diagnostic::________________FUNCTIONS_MISSING_FROM_YOUR_CONCEPT_MAP<decltype(missing)>,
diagnostic::________________FUNCTIONS_DECLARED_IN_YOUR_CONCEPT_MAP<decltype(declared)>,
diagnostic::________________FUNCTIONS_REQUIRED_BY_THE_CONCEPT<decltype(required)>,
diagnostic::________________EXACT_TYPE_OF_YOUR_CONCEPT_MAP<decltype(as_concept_map)>
>();
}
}
} // end namespace dyno
#endif // DYNO_CONCEPT_MAP_HPP

View File

@ -1,57 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_DETAIL_BIND_SIGNATURE_HPP
#define DYNO_DETAIL_BIND_SIGNATURE_HPP
#include <dyno/detail/dsl.hpp>
#include <dyno/detail/transform_signature.hpp>
namespace dyno { namespace detail {
template <typename Old, typename New, typename T>
struct replace_impl { using type = T; };
template <typename Old, typename New>
struct replace_impl<Old, New, Old> { using type = New; };
template <typename Old, typename New>
struct replace_impl<Old, New, Old&> { using type = New&; };
template <typename Old, typename New>
struct replace_impl<Old, New, Old&&> { using type = New&&; };
template <typename Old, typename New>
struct replace_impl<Old, New, Old*> { using type = New*; };
template <typename Old, typename New>
struct replace_impl<Old, New, Old const> { using type = New const; };
template <typename Old, typename New>
struct replace_impl<Old, New, Old const&> { using type = New const&; };
template <typename Old, typename New>
struct replace_impl<Old, New, Old const&&> { using type = New const&&; };
template <typename Old, typename New>
struct replace_impl<Old, New, Old const*> { using type = New const*; };
template <typename Old, typename New>
struct replace {
template <typename T>
using apply = replace_impl<Old, New, T>;
};
// Takes a placeholder signature and replaces instances of `dyno::T` by the given
// type.
//
// Basically, this will turn stuff like `void (dyno::T const&, int, dyno::T*)`
// into `void (T const&, int, T*)`, where `T` is the type we are _binding_
// the signature to.
//
// This is used to make sure that the functions provided in a concept map
// have the right signature.
template <typename Signature, typename T>
using bind_signature = detail::transform_signature<
Signature, detail::replace<dyno::T, T>::template apply
>;
}} // end namespace dyno::detail
#endif // DYNO_DETAIL_BIND_SIGNATURE_HPP

View File

@ -1,146 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_DETAIL_DSL_HPP
#define DYNO_DETAIL_DSL_HPP
#include <boost/hana/bool.hpp>
#include <boost/hana/core/tag_of.hpp>
#include <boost/hana/pair.hpp>
#include <boost/hana/string.hpp>
#include <boost/hana/tuple.hpp>
#include <boost/hana/type.hpp>
#include <type_traits>
#include <utility>
namespace dyno {
template <typename Signature>
struct function_t { using type = Signature; };
template <typename Sig1, typename Sig2>
constexpr auto operator==(function_t<Sig1>, function_t<Sig2>) {
return boost::hana::bool_c<std::is_same<Sig1, Sig2>::value>;
}
template <typename Sig1, typename Sig2>
constexpr auto operator!=(function_t<Sig1> m1, function_t<Sig2> m2) {
return !(m1 == m2);
}
// Right-hand-side of a clause in a concept that signifies a function with the
// given signature.
template <typename Signature>
constexpr function_t<Signature> function{};
template <typename Signature>
struct method_t;
// Right-hand-side of a clause in a concept that signifies a method with the
// given signature. The first parameter of the resulting function is implicitly
// `dyno::T&` for a non-const method, and `dyno::T const&` for a const method.
template <typename Signature>
constexpr method_t<Signature> method{};
// Placeholder type representing the type of ref-unqualified `*this` when
// defining a clause in a concept.
struct T;
template <typename R, typename ...Args>
struct method_t<R(Args...)> { using type = R (dyno::T&, Args...); };
template <typename R, typename ...Args>
struct method_t<R(Args...) &> { using type = R (dyno::T&, Args...); };
template <typename R, typename ...Args>
struct method_t<R(Args...) &&> { using type = R (dyno::T&&, Args...); };
template <typename R, typename ...Args>
struct method_t<R(Args...) const> { using type = R (dyno::T const&, Args...); };
template <typename R, typename ...Args>
struct method_t<R(Args...) const&> { using type = R (dyno::T const&, Args...); };
// const&& not supported because it's stupid
template <typename Sig1, typename Sig2>
constexpr auto operator==(method_t<Sig1>, method_t<Sig2>) {
return boost::hana::bool_c<std::is_same<Sig1, Sig2>::value>;
}
template <typename Sig1, typename Sig2>
constexpr auto operator!=(method_t<Sig1> m1, method_t<Sig2> m2) {
return !(m1 == m2);
}
namespace detail {
template <typename Name, typename ...Args>
struct delayed_call {
boost::hana::tuple<Args...> args;
// All the constructors are private so that only `dyno::string` can
// construct an instance of this. The intent is that we can only
// manipulate temporaries of this type.
private:
template <char ...c> friend struct string;
template <typename ...T>
constexpr delayed_call(T&& ...t) : args{std::forward<T>(t)...} { }
delayed_call(delayed_call const&) = default;
delayed_call(delayed_call&&) = default;
};
template <char ...c>
struct string : boost::hana::string<c...> {
template <typename Function>
constexpr boost::hana::pair<string, Function>
operator=(Function f) const {
static_assert(std::is_empty<Function>{},
"Only stateless function objects can be used to define vtables");
return {{}, f};
}
template <typename ...Args>
constexpr auto operator()(Args&& ...args) const {
return detail::delayed_call<string, Args&&...>{std::forward<Args>(args)...};
}
using hana_tag = typename boost::hana::tag_of<boost::hana::string<c...>>::type;
};
template <typename S, std::size_t ...N>
constexpr detail::string<S::get()[N]...> prepare_string_impl(std::index_sequence<N...>)
{ return {}; }
template <typename S>
constexpr auto prepare_string(S) {
return detail::prepare_string_impl<S>(std::make_index_sequence<S::size()>{});
}
} // end namespace detail
inline namespace literals {
// Creates a compile-time string that can be used as the left-hand-side when
// defining clauses or filling concept maps.
template <typename CharT, CharT ...c>
constexpr auto operator""_s() { return detail::string<c...>{}; }
} // end namespace literals
// Creates a Dyno compile-time string without requiring the use of a
// user-defined literal.
//
// The user-defined literal is non-standard as of C++17, and it requires
// brining the literal in scope (through a using declaration or such),
// which is not always convenient or possible.
#define DYNO_STRING(s) \
(::dyno::detail::prepare_string([]{ \
struct tmp { \
/* exclude null terminator in size() */ \
static constexpr std::size_t size() { return sizeof(s) - 1; } \
static constexpr char const* get() { return s; } \
}; \
return tmp{}; \
}())) \
/**/
} // end namespace dyno
#endif // DYNO_DETAIL_DSL_HPP

View File

@ -1,86 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_DETAIL_EMPTY_OBJECT_HPP
#define DYNO_DETAIL_EMPTY_OBJECT_HPP
#include <type_traits>
namespace dyno { namespace detail {
// Hack to get an instance of an empty type that is not default constructible,
// while still satisfying the C++ standard. None of this would be necessary if
// stateless lambdas were default constructible.
//
// The idea behind this trick is that we make two layout-compatible types `T1`
// and `T2`, while having `T2` inherit from the empty class `T` we're trying
// to bypass the construction of.
//
// We then create a union of these two types, and initialize `T1` inside that
// union. Since `T1` and `T2` are layout-compatible, we can legally access the
// `T2` member of the union, which we can then legally cast to its base class
// `T`. Et voilà, we've got a valid `T` object, yet we've never initialized it.
//
// For details on standard layout types, see:
// http://en.cppreference.com/w/cpp/language/data_members#Standard_layout
template <typename T>
struct empty_object {
static_assert(std::is_standard_layout<T>{},
"This trick won't work if `T` is not standard layout, because that's "
"required for `T2` below to be standard layout.");
static_assert(std::is_empty<T>{},
"This trick won't work if `T` is not empty, because that's required for "
"`T1` and `T2` below to be layout-compatible (and also to make sure `T` "
"is trivially destructible.");
// `T1` and `T2` have `c` as a common initial sequence of non-static
// data members:
// Two standard-layout non-union class types may have a common initial
// sequence of non-static data members and bit-fields, for a sequence of
// one or more initial members (in order of declaration), if the members
// have layout-compatible types [...].
// `T1` and `T2` are layout-compatible:
// Two standard-layout non-union class types are called layout-compatible
// if [...] their common initial sequence consists of every non-static
// data member and bit field.
struct T1 { char c; };
struct T2 : T { char c; };
union Storage {
constexpr Storage() : t1{} { }
T1 t1;
T2 t2;
};
static T get() {
// Initialize the union with `T1` active.
Storage storage{};
// Access the member `c` of `T2` in the union:
// In a standard-layout union with an active member of non-union class
// type `T1`, it is permitted to read a non-static data member `m` of
// another union member of non-union class type `T2` provided `m` is
// part of the common initial sequence of `T1` and `T2` [...].
char const* c = &storage.t2.c;
// From this pointer, get a pointer back to `T2`:
// A pointer to an object of standard-layout struct type can be
// `reinterpret_cast` to pointer to its first non-static data member
// (if it has non-static data members) [...], and vice versa.
// (padding is not allowed before the first data member). [...]
T2 const* t2 = reinterpret_cast<T2 const*>(c);
// Finally, get a pointer to the base class `T` from the `T2` pointer.
T const* t = static_cast<T const*>(t2);
return *t;
}
};
}} // end namespace dyno::detail
#endif // DYNO_DETAIL_EMPTY_OBJECT_HPP

View File

@ -1,73 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_DETAIL_ERASE_FUNCTION_HPP
#define DYNO_DETAIL_ERASE_FUNCTION_HPP
#include <dyno/detail/empty_object.hpp>
#include <dyno/detail/eraser_traits.hpp>
#include <boost/callable_traits/function_type.hpp>
#include <utility>
namespace dyno { namespace detail {
template <typename Eraser, typename F, typename PlaceholderSig, typename ActualSig>
struct thunk;
template <typename Eraser, typename F, typename R_pl, typename ...Args_pl,
typename R_ac, typename ...Args_ac>
struct thunk<Eraser, F, R_pl(Args_pl...), R_ac(Args_ac...)> {
static constexpr auto
apply(typename detail::erase_placeholder<Eraser, Args_pl>::type ...args)
-> typename detail::erase_placeholder<Eraser, R_pl>::type
{
return detail::erase<Eraser, R_pl>::apply(
detail::empty_object<F>::get()(
detail::unerase<Eraser, Args_pl, Args_ac>::apply(
std::forward<typename detail::erase_placeholder<Eraser, Args_pl>::type>(args)
)...
)
);
}
};
template <typename Eraser, typename F, /* void */ typename ...Args_pl,
typename R_ac, typename ...Args_ac>
struct thunk<Eraser, F, void(Args_pl...), R_ac(Args_ac...)> {
static constexpr auto
apply(typename detail::erase_placeholder<Eraser, Args_pl>::type ...args)
-> void
{
detail::empty_object<F>::get()(
detail::unerase<Eraser, Args_pl, Args_ac>::apply(
std::forward<typename detail::erase_placeholder<Eraser, Args_pl>::type>(args)
)...
);
}
};
// Transform an actual (stateless) function object with statically typed
// parameters into a type-erased function suitable for storage in a vtable.
//
// The pointer returned by `erase_function` is what's called a thunk; it
// makes a few adjustments to the arguments (usually 0-overhead static
// casts) and forwards them to another function.
//
// TODO:
// - Would it be possible to erase a callable that's not a stateless function
// object? Would that necessarily require additional storage?
// - Should we be returning a lambda that erases its arguments?
template <typename Signature, typename Eraser = void, typename F>
constexpr auto erase_function(F const&) {
using ActualSignature = boost::callable_traits::function_type_t<F>;
using Thunk = detail::thunk<Eraser, F, Signature, ActualSignature>;
return &Thunk::apply;
}
}} // end namespace dyno::detail
#endif // DYNO_DETAIL_ERASE_FUNCTION_HPP

View File

@ -1,41 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_DETAIL_ERASE_SIGNATURE_HPP
#define DYNO_DETAIL_ERASE_SIGNATURE_HPP
#include <dyno/detail/eraser_traits.hpp>
#include <dyno/detail/transform_signature.hpp>
namespace dyno { namespace detail {
template <typename Eraser>
struct apply_erase_placeholder {
template <typename Placeholder>
using apply = detail::erase_placeholder<Eraser, Placeholder>;
};
// Transforms a signature potentially containing placeholders into a signature
// containing no placeholders, and which would be suitable for storing as a
// function pointer.
//
// Basically, this turns types like `dyno::T&` into `void*` (or what's specified
// by the given `Eraser`) at the top-level of the signature. This is used when
// we need to generate a vtable from a concept definition. The concept defines
// signatures with placeholders, and we need to generate a concrete function
// type that can be stored in a vtable. That concrete type is the result of
// `erase_signature`.
//
// Note that this returns a function type, not a function pointer type.
// For actually storing an object of this type, one needs to add a pointer
// qualifier to it.
template <typename Signature, typename Eraser = void>
using erase_signature = detail::transform_signature<
Signature, detail::apply_erase_placeholder<Eraser>::template apply
>;
}} // end namespace dyno::detail
#endif // DYNO_DETAIL_ERASE_SIGNATURE_HPP

View File

@ -1,148 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_DETAIL_ERASER_TRAITS_HPP
#define DYNO_DETAIL_ERASER_TRAITS_HPP
#include <dyno/detail/dsl.hpp>
#include <type_traits>
#include <utility>
namespace dyno { namespace detail {
// Traits for types that can be used to erase other types. The following traits
// should be provided:
//
// template <typename Eraser, typename Placeholder>
// struct erase_placeholder;
//
// Metafunction transforming the type of a possibly cv and ref-qualified
// placeholder (a `dyno::T`) into a representation suitable for passing
// around as a parameter to an erased function. Basically, this turns a
// type like `dyno::T&` into a `void*`, and similarly for other types of
// possibly, const or ref-qualified `dyno::T`s, but it can be customized
// for custom eraser types.
//
// template <typename Eraser, typename Placeholder, typename Actual>
// static constexpr erase_placeholder<Eraser, Placeholder>::type
// erase<Eraser, Placeholder>::apply(Actual);
//
// Function transforming an object to the generic representation for the
// given placeholder for that eraser. This is used to obtain a representation
// of the object that can be passed to an erased function stored in a vtable.
// For example, in the case of the `void` eraser, this function just takes a
// reference or a pointer to an actual type and passes it as a `void*`, with
// the proper cv qualifiers. Note that an eraser is not expected to support
// erasure of arbitrary types. For example, it is perfectly fine to give an
// error if one tries to erase an `int` as a `dyno::T&`, since that makes no
// sense. However, it is probably a good idea to support inexact casts that
// do make sense, such as erasing `int&` to `dyno::T const&` (even though the
// cv-qualifiers don't match). This is left to the implementation of the
// specific eraser.
//
// template <typename Eraser, typename Placeholder, typename Actual>
// static constexpr Actual
// unerase<Eraser, Placeholder, Actual>
// ::apply(erase_placeholder<Eraser, Placeholder>::type)
//
// This is the inverse operation to `erase`. It takes an object that was
// erased and interprets it as an object of the specified `Actual` type.
// The eraser can assume that the object that was erased is indeed of the
// requested type. This function is used to transform an object with an
// erased representation into an object that can be passed to a function
// stored in a concept map.
template <typename Eraser, typename Placeholder>
struct erase_placeholder {
static_assert(!std::is_same<Placeholder, dyno::T>{},
"dyno::T may not be passed by value; it is only a placeholder");
using type = Placeholder;
};
template <typename Eraser, typename Placeholder>
struct erase {
template <typename Arg>
static constexpr decltype(auto) apply(Arg&& arg) {
return std::forward<Arg>(arg);
}
};
template <typename Eraser, typename Placeholder, typename Actual>
struct unerase {
template <typename Arg>
static constexpr decltype(auto) apply(Arg&& arg)
{ return std::forward<Arg>(arg); }
};
// Specialization for the `void` Eraser, which uses `void*` to erase types.
template <> struct erase_placeholder<void, dyno::T const&> { using type = void const*; };
template <> struct erase_placeholder<void, dyno::T&> { using type = void*; };
template <> struct erase_placeholder<void, dyno::T&&> { using type = void*; };
template <> struct erase_placeholder<void, dyno::T*> { using type = void*; };
template <> struct erase_placeholder<void, dyno::T const*> { using type = void const*; };
template <>
struct erase<void, dyno::T const&> {
template <typename Arg>
static constexpr void const* apply(Arg const& arg)
{ return &arg; }
};
template <>
struct erase<void, dyno::T&> {
template <typename Arg>
static constexpr void* apply(Arg& arg)
{ return &arg; }
};
template <>
struct erase<void, dyno::T&&> {
template <typename Arg>
static constexpr void* apply(Arg&& arg) {
static_assert(std::is_rvalue_reference<Arg>::value, "will move from non-rvalue");
return &arg;
}
};
template <>
struct erase<void, dyno::T const*> {
template <typename Arg>
static constexpr void const* apply(Arg const* arg)
{ return arg; }
};
template <>
struct erase<void, dyno::T*> {
template <typename Arg>
static constexpr void* apply(Arg* arg)
{ return arg; }
};
template <typename Actual>
struct unerase<void, dyno::T&, Actual&> {
static constexpr Actual& apply(void* arg)
{ return *static_cast<Actual*>(arg); }
};
template <typename Actual>
struct unerase<void, dyno::T const&, Actual const&> {
static constexpr Actual const& apply(void const* arg)
{ return *static_cast<Actual const*>(arg); }
};
template <typename Actual>
struct unerase<void, dyno::T&&, Actual&&> {
static constexpr Actual&& apply(void* arg)
{ return std::move(*static_cast<Actual*>(arg)); }
};
template <typename Actual>
struct unerase<void, dyno::T*, Actual*> {
static constexpr Actual* apply(void* arg)
{ return static_cast<Actual*>(arg); }
};
template <typename Actual>
struct unerase<void, dyno::T const*, Actual const*> {
static constexpr Actual const* apply(void const* arg)
{ return static_cast<Actual const*>(arg); }
};
}} // end namespace dyno::detail
#endif // DYNO_DETAIL_ERASER_TRAITS_HPP

View File

@ -1,30 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_DETAIL_HAS_DUPLICATES_HPP
#define DYNO_DETAIL_HAS_DUPLICATES_HPP
#include <boost/hana/core/to.hpp>
#include <boost/hana/length.hpp>
#include <boost/hana/not_equal.hpp>
#include <boost/hana/set.hpp>
namespace dyno { namespace detail {
// Returns whether a Foldable contains duplicate elements, i.e. elements
// that compare equal to each other.
//
// TODO: Lift this into the public interface of Hana.
template <typename Foldable>
constexpr auto has_duplicates(Foldable const& foldable) {
return boost::hana::not_equal(
boost::hana::length(boost::hana::to_set(foldable)),
boost::hana::length(foldable)
);
}
}} // end namespace dyno::detail
#endif // DYNO_DETAIL_HAS_DUPLICATES_HPP

View File

@ -1,30 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_DETAIL_IS_PLACEHOLDER_HPP
#define DYNO_DETAIL_IS_PLACEHOLDER_HPP
#include <dyno/detail/dsl.hpp>
#include <type_traits>
namespace dyno { namespace detail {
// Metafunction returning whether a type is a possibly const/ref-qualified
// placeholder, or a pointer to one.
template <typename T>
struct is_placeholder : std::false_type { };
template <> struct is_placeholder<dyno::T&> : std::true_type { };
template <> struct is_placeholder<dyno::T&&> : std::true_type { };
template <> struct is_placeholder<dyno::T*> : std::true_type { };
template <> struct is_placeholder<dyno::T const&> : std::true_type { };
template <> struct is_placeholder<dyno::T const&&> : std::true_type { };
template <> struct is_placeholder<dyno::T const*> : std::true_type { };
}} // end namespace dyno::detail
#endif // DYNO_DETAIL_IS_PLACEHOLDER_HPP

View File

@ -1,124 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_MACRO_HPP
#define DYNO_MACRO_HPP
//////////////////////////////////////////////////////////////////////////////
// THIS FILE WAS GENERATED; DO NOT EDIT DIRECTLY.
//
//
// This file is generated from the <dyno/detail/macro.hpp.erb> ERB[1] template
// file. The maximum number of methods that can be handled by the macros can
// be controlled with the 'MAX_NUMBER_OF_METHODS' variable, which can be set
// when calling ERB to generate the header:
//
// export MAX_NUMBER_OF_METHODS=55
// erb include/dyno/detail/macro.hpp.erb > include/dyno/macro.hpp
//
// Due to the limits of some preprocessor macros we're using in the implementation,
// 'MAX_NUMBER_OF_METHODS' must be <= 62, otherwise an error is triggered.
// If 'MAX_NUMBER_OF_METHODS' is not specified, it defaults to 20.
//
// [1]: http://en.wikipedia.org/wiki/ERuby
//////////////////////////////////////////////////////////////////////////////
<%
MAX_NUMBER_OF_METHODS = (ENV["MAX_NUMBER_OF_METHODS"] || 20).to_i
raise "MAX_NUMBER_OF_METHODS must be <= 62" if MAX_NUMBER_OF_METHODS > 62
%>
#include <dyno/concept.hpp>
#include <dyno/concept_map.hpp>
#include <dyno/detail/dsl.hpp>
#include <dyno/detail/preprocessor.hpp>
#include <dyno/poly.hpp>
#include <boost/callable_traits/function_type.hpp>
#include <boost/callable_traits/is_const_member.hpp>
#include <boost/callable_traits/return_type.hpp>
#include <type_traits>
#include <utility>
// TODOS
// - Allow specifying custom base concepts and base interfaces. By default, a
// concept could provide a default interface (e.g. a copy constructor for
// CopyConstructible, etc..).
// - Handle const and non-const more gracefully
// - Try to get rid of commas in the method definition. Use a BOOST_PP_SEQ
#define DYNO_INTERFACE(name, ...) \
DYNO_PP_CONCAT(DYNO_PP_INTERFACE_IMPL_, DYNO_PP_NARG(__VA_ARGS__))(name, __VA_ARGS__)
<% (0..MAX_NUMBER_OF_METHODS).each do |n|
args = (1..n).map { |i| "arg#{i}" }
def dyno_string(arg)
"DYNO_STRING(DYNO_PP_STRINGIZE(DYNO_PP_VARIADIC_HEAD #{arg}))"
end
%>
#define DYNO_PP_INTERFACE_IMPL_<%= n %>(name<%= args.map { |arg| ", #{arg}" }.join %>)\
struct DYNO_PP_CONCAT(dyno_concept_for_, name) { \
static auto make_type() { \
return ::dyno::requires( \
<% args.each_with_index do |arg, i| %> \
<%= ',' if i > 0 %> \
<%= dyno_string(arg) %> = ::dyno::method<DYNO_PP_VARIADIC_TAIL <%= arg %>>\
<% end %> \
); \
} \
}; \
\
class name { \
struct concept_t \
: decltype(DYNO_PP_CONCAT(dyno_concept_for_, name)::make_type()) \
{ }; \
public: \
template <typename T> \
name(T&& x) \
: poly_{static_cast<T&&>(x), ::dyno::make_concept_map( \
<% args.each_with_index do |arg, i| %> \
<%= ',' if i > 0 %> \
<%= dyno_string(arg) %> = [](auto&& self, auto&& ...args) -> decltype(auto) {\
return static_cast<decltype(self)&&>(self) \
.DYNO_PP_VARIADIC_HEAD <%= arg %> \
(static_cast<decltype(args)&&>(args)...); \
} \
<% end %> \
)} \
{ } \
<% args.each_with_index do |arg, i| %> \
template <typename ...Args, typename = decltype( \
::std::declval<::boost::callable_traits::function_type_t<DYNO_PP_VARIADIC_TAIL <%= arg %>>>()\
(::std::declval<Args&&>()...) \
), typename = ::std::enable_if_t< \
!::boost::callable_traits::is_const_member<DYNO_PP_VARIADIC_TAIL <%= arg %>>::value,\
char[sizeof...(Args)+1] /* make the enable_if dependent */ \
>> \
::boost::callable_traits::return_type_t<DYNO_PP_VARIADIC_TAIL <%= arg %>>\
DYNO_PP_VARIADIC_HEAD <%= arg %>(Args&& ...args) { \
return poly_.virtual_(<%= dyno_string(arg) %>)(static_cast<Args&&>(args)...);\
} \
template <typename ...Args, typename = decltype( \
::std::declval<::boost::callable_traits::function_type_t<DYNO_PP_VARIADIC_TAIL <%= arg %>>>()\
(::std::declval<Args&&>()...) \
), typename = ::std::enable_if_t< \
::boost::callable_traits::is_const_member<DYNO_PP_VARIADIC_TAIL <%= arg %>>::value,\
char[sizeof...(Args)+1] /* make the enable_if dependent */ \
>> \
::boost::callable_traits::return_type_t<DYNO_PP_VARIADIC_TAIL <%= arg %>>\
DYNO_PP_VARIADIC_HEAD <%= arg %>(Args&& ...args) const { \
return poly_.virtual_(<%= dyno_string(arg) %>)(static_cast<Args&&>(args)...);\
} \
<% end %> \
private: \
::dyno::poly<concept_t> poly_; \
}
<% end %>
#endif // DYNO_MACRO_HPP

View File

@ -1,31 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_DETAIL_PREPROCESSOR_HPP
#define DYNO_DETAIL_PREPROCESSOR_HPP
// Macro expanding to the number of arguments it is passed.
//
// Specifically, `DYNO_PP_NARG(x1, ..., xn)` expands to `n`.
// The program is ill-formed if `n > 64` or if `n == 0`.
#define DYNO_PP_NARG(...) \
DYNO_PP_NARG_IMPL(__VA_ARGS__, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,)
#define DYNO_PP_NARG_IMPL(e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26, e27, e28, e29, e30, e31, e32, e33, e34, e35, e36, e37, e38, e39, e40, e41, e42, e43, e44, e45, e46, e47, e48, e49, e50, e51, e52, e53, e54, e55, e56, e57, e58, e59, e60, e61, e62, e63, N, ...) N
// Expands to the concatenation of its two arguments.
#define DYNO_PP_CONCAT(x, y) DYNO_PP_CONCAT_PRIMITIVE(x, y)
#define DYNO_PP_CONCAT_PRIMITIVE(x, y) x ## y
// Expands to the stringized version of its argument.
#define DYNO_PP_STRINGIZE(...) DYNO_PP_STRINGIZE_PRIMITIVE(__VA_ARGS__)
#define DYNO_PP_STRINGIZE_PRIMITIVE(...) #__VA_ARGS__
// Expands to its first argument.
#define DYNO_PP_VARIADIC_HEAD(head, ...) head
// Expands to all the arguments beyond the first one.
#define DYNO_PP_VARIADIC_TAIL(head, ...) __VA_ARGS__
#endif // DYNO_DETAIL_PREPROCESSOR_HPP

View File

@ -1,30 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_DETAIL_TRANSFORM_SIGNATURE_HPP
#define DYNO_DETAIL_TRANSFORM_SIGNATURE_HPP
namespace dyno { namespace detail {
// Transforms a signature by applying a metafunction to the return type and
// all the arguments of a function signature. This returns a function type,
// not a pointer to a function.
template <typename Signature, template <typename ...> class F>
struct transform_signature;
template <typename R, typename ...Args, template <typename ...> class F>
struct transform_signature<R (Args...), F> {
using Result = typename F<R>::type;
using type = Result (typename F<Args>::type...);
};
template <typename R, typename ...Args, template <typename ...> class F>
struct transform_signature<R (Args..., ...), F> {
using Result = typename F<R>::type;
using type = Result (typename F<Args>::type..., ...);
};
}} // end namespace dyno::detail
#endif // DYNO_DETAIL_TRANSFORM_SIGNATURE_HPP

View File

@ -1,69 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_EXPERIMENTAL_UNROLLED_VTABLE_HPP
#define DYNO_EXPERIMENTAL_UNROLLED_VTABLE_HPP
#include <dyno/concept.hpp>
#include <dyno/detail/erase_function.hpp>
#include <dyno/detail/erase_signature.hpp>
#include <boost/hana/functional/on.hpp>
#include <boost/hana/pair.hpp>
#include <boost/hana/type.hpp>
#include <boost/hana/unpack.hpp>
namespace dyno { namespace experimental {
namespace detail {
template <typename ...Mappings>
struct unrolled_vtable_impl;
}
template <typename Concept>
using unrolled_vtable = typename decltype(
boost::hana::unpack(dyno::clauses(Concept{}),
boost::hana::template_<detail::unrolled_vtable_impl> ^boost::hana::on^ boost::hana::decltype_
)
)::type;
/*
What follows is a script to generate the specializations of `unrolled_vtable_impl`.
Run the script as
awk '/SCRIPTBEGIN/{flag=1;next}/SCRIPTEND/{flag=0}flag' ${thisfile} | erb
SCRIPTBEGIN
<% (0..10).each do |n| %>
template <
<%= (0...n).map {|i| "typename Name#{i}, typename Sig#{i}" }.join(",\n ") %>
> struct unrolled_vtable_impl<
<%= (0...n).map {|i| "boost::hana::pair<Name#{i}, boost::hana::basic_type<Sig#{i}>>" }.join(",\n ") %>
> {
template <typename ConceptMap>
constexpr explicit unrolled_vtable_impl(ConceptMap map)
<%= ": " unless n == 0 %><%= (0...n).map {|i| "fptr#{i}_(dyno::detail::erase_function<Sig#{i}>(map[Name#{i}{}]))" }.join("\n , ") %>
{ }
<%= (0...n).map {|i| "constexpr auto operator[](Name#{i}) const { return fptr#{i}_; };" }.join("\n ") %>
private:
<%= (0...n).map {|i| "typename detail::erase_signature<Sig#{i}>::type* fptr#{i}_;" }.join("\n ") %>
};
<% end %>
SCRIPTEND */
namespace detail {
////////////////////////////////////////////////////////////////////////////
// BEGIN GENERATED CODE
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// END GENERATED CODE
////////////////////////////////////////////////////////////////////////////
} // end namespace detail
}} // end namespace dyno::experimental
#endif // DYNO_EXPERIMENTAL_UNROLLED_VTABLE_HPP

View File

@ -1,103 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_EXPERIMENTAL_VTABLE_HPP
#define DYNO_EXPERIMENTAL_VTABLE_HPP
#include <dyno/concept.hpp>
#include <dyno/detail/erase_function.hpp>
#include <type_traits>
#include <utility>
namespace dyno {
namespace experimental {
namespace detail {
template <typename ...Functions>
struct vtable_base;
template <typename Concept, typename ConceptMap, typename ...Functions>
struct vtable_impl;
template <typename Concept, typename ConceptMap>
struct make_vtable_impl {
template <typename ...Functions>
using apply = vtable_impl<Concept, ConceptMap, Functions...>;
};
} // end namespace detail
// TODO: This vtable is currently broken since we have removed `unpack_vtable_layout`.
template <typename Concept>
struct vtable {
#if 0
template <typename ConceptMap>
constexpr explicit vtable(ConceptMap) {
using Derived = dyno::unpack_vtable_layout<
Concept, detail::make_vtable_impl<Concept, ConceptMap>::template apply
>;
new (&base_) Derived{};
}
template <typename Name>
constexpr auto operator[](Name) const {
Base const* base = static_cast<Base const*>(static_cast<void const*>(&base_));
return [base](auto&& ...args) -> decltype(auto) {
return base->apply(Name{}, std::forward<decltype(args)>(args)...);
};
}
private:
using Base = dyno::unpack_vtable_layout<Concept, detail::vtable_base>;
std::aligned_storage<sizeof(Base)> base_;
#endif
};
/*
What follows is a script to generate the specialization of `vtable_base`
and `vtable_impl`. Run the script as
awk '/SCRIPTBEGIN/{flag=1;next}/SCRIPTEND/{flag=0}flag' ${thisfile} | erb
SCRIPTBEGIN
<% (0..10).each do |n| %>
template <
<%= (0...n).map {|i| "typename Name#{i}, typename R#{i}, typename ...Args#{i}" }.join(",\n ") %>
> struct vtable_base<
<%= (0...n).map {|i| "std::pair<Name#{i}, R#{i} (*)(Args#{i}...)>" }.join(",\n ") %>
> {
<%= (0...n).map {|i| "virtual R#{i} apply(Name#{i}, Args#{i}...) const = 0;" }.join("\n ") %>
};
template <typename Concept, typename ConceptMap
<%= (0...n).map {|i| ", typename Name#{i}, typename R#{i}, typename ...Args#{i}" }.join("\n ") %>
>
struct vtable_impl<Concept, ConceptMap
<%= (0...n).map {|i| ", std::pair<Name#{i}, R#{i} (*)(Args#{i}...)>" }.join("\n ") %>
> final : vtable_base<
<%= (0...n).map {|i| "std::pair<Name#{i}, R#{i} (*)(Args#{i}...)>" }.join(",\n ") %>
> {
<%= (0...n).map {|i|
"virtual R#{i} apply(Name#{i} name, Args#{i} ...args) const override final" +
"{ return dyno::detail::erase_function<typename decltype(Concept{}.get_signature(name))::type>(ConceptMap{}[name])(std::forward<Args#{i}>(args)...); }"
}.join("\n ") %>
};
<% end %>
SCRIPTEND */
namespace detail {
////////////////////////////////////////////////////////////////////////////
// BEGIN GENERATED CODE
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// END GENERATED CODE
////////////////////////////////////////////////////////////////////////////
} // end namespace detail
} // end namespace experimental
} // end namespace dyno
#endif // DYNO_EXPERIMENTAL_VTABLE_HPP

File diff suppressed because it is too large Load Diff

View File

@ -1,260 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_POLY_HPP
#define DYNO_POLY_HPP
#include <dyno/builtin.hpp>
#include <dyno/concept.hpp>
#include <dyno/concept_map.hpp>
#include <dyno/detail/is_placeholder.hpp>
#include <dyno/storage.hpp>
#include <dyno/vtable.hpp>
#include <boost/hana/contains.hpp>
#include <boost/hana/core/to.hpp>
#include <boost/hana/map.hpp>
#include <boost/hana/unpack.hpp>
#include <type_traits>
#include <utility>
namespace dyno {
// A `dyno::poly` encapsulates an object of a polymorphic type that supports the
// interface of the given `Concept`.
//
// `dyno::poly` is meant to be used as a holder for a polymorphic object. It can
// manage the lifetime of that object and provide access to its dynamically-
// dispatched methods. However, it does not directly implement any specific
// interface beyond what's strictly necessary for managing the lifetime
// (constructor, destructor, assignment, swap, etc..). Instead, it provides
// a `virtual_` method that gives access to dynamically-dispatched methods of
// the object it manages.
//
// The intended use case is for users to create their very own type-erased
// wrappers on top of `dyno::poly`, defining their interface as they wish and
// using the dynamic dispatch provided by the library to implement runtime
// polymorphism.
//
// Different aspects of a `dyno::poly` can also be customized:
// `Concept`
// The concept satisfied by `dyno::poly`. This determines which methods will
// be available for dynamic dispatching.
//
// `Storage`
// The type used to provide the storage for the managed object. This must
// be a model of the `PolymorphicStorage` concept.
//
// `VTable`
// The policy specifying how to implement the dynamic dispatching mechanism
// for methods. This must be a specialization of `dyno::vtable`.
// See `dyno::vtable` for details.
//
// TODO:
// - How to combine the storage of the object with that of the vtable?
// For example, how would we allow storing the vtable inside the rest
// of the storage?
// - Is it actually OK to require Destructible and Storable all the time?
// - Test that we can't call e.g. a non-const method on a const poly.
template <
typename Concept,
typename Storage = dyno::remote_storage,
typename VTablePolicy = dyno::vtable<dyno::remote<dyno::everything>>
>
struct poly {
private:
using ActualConcept = decltype(dyno::requires(
Concept{},
dyno::Destructible{},
dyno::Storable{}
));
using VTable = typename VTablePolicy::template apply<ActualConcept>;
public:
template <typename T, typename RawT = std::decay_t<T>, typename ConceptMap>
poly(T&& t, ConceptMap map)
: vtable_{dyno::complete_concept_map<ActualConcept, RawT>(map)}
, storage_{std::forward<T>(t)}
{ }
template <typename T, typename RawT = std::decay_t<T>,
typename = std::enable_if_t<!std::is_same<RawT, poly>::value>,
typename = std::enable_if_t<dyno::models<ActualConcept, RawT>>
>
poly(T&& t)
: poly{std::forward<T>(t), dyno::concept_map<ActualConcept, RawT>}
{ }
poly(poly const& other)
: vtable_{other.vtable_}
, storage_{other.storage_, vtable_}
{ }
poly(poly&& other)
: vtable_{std::move(other.vtable_)}
, storage_{std::move(other.storage_), vtable_}
{ }
poly& operator=(poly const& other) {
poly(other).swap(*this);
return *this;
}
poly& operator=(poly&& other) {
poly(std::move(other)).swap(*this);
return *this;
}
void swap(poly& other) {
storage_.swap(vtable_, other.storage_, other.vtable_);
using std::swap;
swap(vtable_, other.vtable_);
}
friend void swap(poly& a, poly& b) { a.swap(b); }
~poly() { storage_.destruct(vtable_); }
template <typename ...T, typename Name, typename ...Args>
decltype(auto) operator->*(dyno::detail::delayed_call<Name, Args...>&& delayed) {
auto f = virtual_(Name{});
auto injected = [f,this](auto&& ...args) -> decltype(auto) {
return f(*this, static_cast<decltype(args)&&>(args)...);
};
return boost::hana::unpack(std::move(delayed.args), injected);
}
template <typename Function,
bool HasClause = decltype(boost::hana::contains(dyno::clause_names(Concept{}), Function{})){},
std::enable_if_t<HasClause>* = nullptr
>
constexpr decltype(auto) virtual_(Function name) const& {
auto clauses = boost::hana::to_map(dyno::clauses(Concept{}));
return virtual_impl(clauses[name], name);
}
template <typename Function,
bool HasClause = decltype(boost::hana::contains(dyno::clause_names(Concept{}), Function{})){},
std::enable_if_t<HasClause>* = nullptr
>
constexpr decltype(auto) virtual_(Function name) & {
auto clauses = boost::hana::to_map(dyno::clauses(Concept{}));
return virtual_impl(clauses[name], name);
}
template <typename Function,
bool HasClause = decltype(boost::hana::contains(dyno::clause_names(Concept{}), Function{})){},
std::enable_if_t<HasClause>* = nullptr
>
constexpr decltype(auto) virtual_(Function name) && {
auto clauses = boost::hana::to_map(dyno::clauses(Concept{}));
return std::move(*this).virtual_impl(clauses[name], name);
}
template <typename Function,
bool HasClause = decltype(boost::hana::contains(dyno::clause_names(Concept{}), Function{})){},
std::enable_if_t<!HasClause>* = nullptr
>
constexpr decltype(auto) virtual_(Function) const {
static_assert(HasClause, "dyno::poly::virtual_: Trying to access a function "
"that is not part of the Concept");
}
// Returns a pointer to the underlying storage.
//
// The pointer is potentially invalidated whenever the poly is modified;
// the specific storage policy should be consulted to know when pointers
// to the underlying storage are invalidated.
//
// The behavior is undefined if the requested type is not cv-qualified `void`
// and the underlying storage is not of the requested type.
template <typename T>
T* unsafe_get() { return storage_.template get<T>(); }
template <typename T>
T const* unsafe_get() const { return storage_.template get<T>(); }
private:
VTable vtable_;
Storage storage_;
// Handle dyno::function
template <typename R, typename ...T, typename Function>
constexpr decltype(auto) virtual_impl(dyno::function_t<R(T...)>, Function name) const {
auto fptr = vtable_[name];
return [fptr](auto&& ...args) -> decltype(auto) {
return fptr(poly::unerase_poly<T>(static_cast<decltype(args)&&>(args))...);
};
}
// Handle dyno::method
template <typename R, typename ...T, typename Function>
constexpr decltype(auto) virtual_impl(dyno::method_t<R(T...)>, Function name) & {
auto fptr = vtable_[name];
return [fptr, this](auto&& ...args) -> decltype(auto) {
return fptr(poly::unerase_poly<dyno::T&>(*this),
poly::unerase_poly<T>(static_cast<decltype(args)&&>(args))...);
};
}
template <typename R, typename ...T, typename Function>
constexpr decltype(auto) virtual_impl(dyno::method_t<R(T...)&>, Function name) & {
auto fptr = vtable_[name];
return [fptr, this](auto&& ...args) -> decltype(auto) {
return fptr(poly::unerase_poly<dyno::T&>(*this),
poly::unerase_poly<T>(static_cast<decltype(args)&&>(args))...);
};
}
template <typename R, typename ...T, typename Function>
constexpr decltype(auto) virtual_impl(dyno::method_t<R(T...)&&>, Function name) && {
auto fptr = vtable_[name];
return [fptr, this](auto&& ...args) -> decltype(auto) {
return fptr(poly::unerase_poly<dyno::T&&>(*this),
poly::unerase_poly<T>(static_cast<decltype(args)&&>(args))...);
};
}
template <typename R, typename ...T, typename Function>
constexpr decltype(auto) virtual_impl(dyno::method_t<R(T...) const>, Function name) const {
auto fptr = vtable_[name];
return [fptr, this](auto&& ...args) -> decltype(auto) {
return fptr(poly::unerase_poly<dyno::T const&>(*this),
poly::unerase_poly<T>(static_cast<decltype(args)&&>(args))...);
};
}
template <typename R, typename ...T, typename Function>
constexpr decltype(auto) virtual_impl(dyno::method_t<R(T...) const&>, Function name) const {
auto fptr = vtable_[name];
return [fptr, this](auto&& ...args) -> decltype(auto) {
return fptr(poly::unerase_poly<dyno::T const&>(*this),
poly::unerase_poly<T>(static_cast<decltype(args)&&>(args))...);
};
}
// unerase_poly helper
template <typename T, typename Arg, std::enable_if_t<!detail::is_placeholder<T>::value, int> = 0>
static constexpr decltype(auto) unerase_poly(Arg&& arg)
{ return static_cast<Arg&&>(arg); }
template <typename T, typename Arg, std::enable_if_t<detail::is_placeholder<T>::value, int> = 0>
static constexpr decltype(auto) unerase_poly(Arg&& arg) {
using RawArg = std::remove_cv_t<std::remove_reference_t<Arg>>;
constexpr bool is_poly = std::is_same<poly, RawArg>::value;
static_assert(is_poly,
"dyno::poly::virtual_: Passing a non-poly object as an argument to a virtual "
"function that specified a placeholder for that parameter.");
return static_cast<Arg&&>(arg).storage_.get();
}
template <typename T, typename Arg, std::enable_if_t<detail::is_placeholder<T>::value, int> = 0>
static constexpr decltype(auto) unerase_poly(Arg* arg) {
using RawArg = std::remove_cv_t<Arg>;
constexpr bool is_poly = std::is_same<poly, RawArg>::value;
static_assert(is_poly,
"dyno::poly::virtual_: Passing a non-poly object as an argument to a virtual "
"function that specified a placeholder for that parameter.");
return arg->storage_.get();
}
};
} // end namespace dyno
#endif // DYNO_POLY_HPP

View File

@ -1,662 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_STORAGE_HPP
#define DYNO_STORAGE_HPP
#include <dyno/builtin.hpp>
#include <dyno/detail/dsl.hpp>
#include <cassert>
#include <cstddef>
#include <cstdlib>
#include <memory>
#include <type_traits>
#include <utility>
namespace dyno {
// concept PolymorphicStorage
//
// The PolymorphicStorage concept represents storage that can be used to store
// an object of an arbitrary type. In a sense, it is like a special-purpose
// allocator that could only ever allocate a single object.
//
// The nice thing about these PolymorphicStorage classes is that they have a
// single type and their ABI does not change even when the type of what they
// hold changes. However, they provide this functionality at the cost of
// "forgetting" the type information of what they hold, which is why they
// must be passed a vtable to perform most operations.
//
// A type `Storage` satisfying the `PolymorphicStorage` concept must provide
// the following functions as part of its interface:
//
// template <typename T> explicit Storage(T&&);
// Semantics: Construct an object of type `std::decay_t<T>` in the polymorphic
// storage, forward the argument to the constructor of the object
// being created. A particular `Storage` class is not expected to
// support being constructed from any such `T`; for example, `T`
// could be too large to fit in a predefined buffer size, in which
// case this call would not compile.
//
// template <typename VTable> Storage(Storage const&, VTable const&);
// Semantics: Copy-construct the contents of the polymorphic storage,
// assuming the contents of the source storage can be
// manipulated using the provided vtable.
//
// template <typename VTable> Storage(Storage&&, VTable const&);
// Semantics: Move-construct the contents of the polymorphic storage,
// assuming the contents of the source storage can be
// manipulated using the provided vtable.
//
// template <typename MyVTable, typename OtherVTable>
// void swap(MyVTable const&, Storage&, OtherVTable const&);
// Semantics: Swap the contents of the two polymorphic storages, assuming
// `*this` can be manipulated using `MyVTable` and the other
// storage can be manipulated using `OtherVTable`.
//
// template <typename VTable> void destruct(VTable const&);
// Semantics: Destruct the object held inside the polymorphic storage, assuming
// that object can be manipulated using the provided vtable. This
// must also free any resource required for storing the object.
// However, this is not the same as destructing the polymorphic
// storage itself (the wrapper), for which the destructor must
// still be called.
//
// WARNING: Since this is not using the usual destructor mechanism,
// it is of utmost importance that users of these storage classes
// call the `destruct` method explicitly in their destructor.
// Furthermore, it should be noted that if an exception is thrown
// in the constructor of a class (say `any_iterator`) using a
// storage class defined here, the cleanup will NOT be done
// automatically because the destructor of `any_iterator` will
// not be called.
//
// template <typename T = void> T* get();
// Semantics: Return a pointer of type `T` to the object inside the polymorphic
// storage. If `T` is not the actual type of the object stored
// inside the polymorphic storage, the behavior is undefined.
//
// template <typename T = void> T const* get() const;
// Semantics: Return a pointer of type `T` to the object inside the polymorphic
// storage. If `T` is not the actual type of the object stored
// inside the polymorphic storage, the behavior is undefined.
//
// static constexpr bool can_store(dyno::storage_info);
// Semantics: Return whether the polymorphic storage can store an object with
// the specified type information.
// Class implementing the small buffer optimization (SBO).
//
// This class represents a value of an unknown type that is stored either on
// the heap, or on the stack if it fits in the specific small buffer size.
//
// TODO: - Consider having ptr_ always point to either sb_ or the heap.
// - Alternatively, if we had a way to access the vtable here, we could
// retrieve the size of the type from it and get rid of `uses_heap_`.
// - We could also use the low bits of the pointer to the vtable for
// `uses_heap_`.
template <std::size_t Size, std::size_t Align = -1u>
class sbo_storage {
static constexpr std::size_t SBSize = Size < sizeof(void*) ? sizeof(void*) : Size;
static constexpr std::size_t SBAlign = Align == -1u ? alignof(std::aligned_storage_t<SBSize>) : Align;
using SBStorage = std::aligned_storage_t<SBSize, SBAlign>;
union {
void* ptr_;
SBStorage sb_;
};
// TODO: It might be possible to pack this bool inside the union somehow.
bool uses_heap_;
public:
sbo_storage() = delete;
sbo_storage(sbo_storage const&) = delete;
sbo_storage(sbo_storage&&) = delete;
sbo_storage& operator=(sbo_storage&&) = delete;
sbo_storage& operator=(sbo_storage const&) = delete;
static constexpr bool can_store(dyno::storage_info info) {
return info.size <= sizeof(SBStorage) && alignof(SBStorage) % info.alignment == 0;
}
template <typename T, typename RawT = std::decay_t<T>>
explicit sbo_storage(T&& t) {
// TODO: We could also construct the object at an aligned address within
// the buffer, which would require computing the right address everytime
// we access the buffer as a T, but would allow more Ts to fit in the SBO.
if constexpr (can_store(dyno::storage_info_for<RawT>)) {
uses_heap_ = false;
new (&sb_) RawT(std::forward<T>(t));
} else {
uses_heap_ = true;
ptr_ = std::malloc(sizeof(RawT));
// TODO: Allocating and then calling the constructor is not
// exception-safe if the constructor throws.
// TODO: That's not a really nice way to handle this
assert(ptr_ != nullptr && "std::malloc failed, we're doomed");
new (ptr_) RawT(std::forward<T>(t));
}
}
template <typename VTable>
sbo_storage(sbo_storage const& other, VTable const& vtable) {
if (other.uses_heap()) {
auto info = vtable["storage_info"_s]();
uses_heap_ = true;
ptr_ = std::malloc(info.size);
// TODO: That's not a really nice way to handle this
assert(ptr_ != nullptr && "std::malloc failed, we're doomed");
vtable["copy-construct"_s](ptr_, other.get());
} else {
uses_heap_ = false;
vtable["copy-construct"_s](&sb_, other.get());
}
}
template <typename VTable>
sbo_storage(sbo_storage&& other, VTable const& vtable)
: uses_heap_{other.uses_heap()}
{
if (uses_heap()) {
this->ptr_ = other.ptr_;
other.ptr_ = nullptr;
} else {
vtable["move-construct"_s](this->get(), other.get());
}
}
template <typename MyVTable, typename OtherVTable>
void swap(MyVTable const& this_vtable, sbo_storage& other, OtherVTable const& other_vtable) {
if (this == &other)
return;
if (this->uses_heap()) {
if (other.uses_heap()) {
std::swap(this->ptr_, other.ptr_);
} else {
void *ptr = this->ptr_;
// Bring `other`'s contents to `*this`, destructively
other_vtable["move-construct"_s](&this->sb_, &other.sb_);
other_vtable["destruct"_s](&other.sb_);
this->uses_heap_ = false;
// Bring `*this`'s stuff to `other`
other.ptr_ = ptr;
other.uses_heap_ = true;
}
} else {
if (other.uses_heap()) {
void *ptr = other.ptr_;
// Bring `*this`'s contents to `other`, destructively
this_vtable["move-construct"_s](&other.sb_, &this->sb_);
this_vtable["destruct"_s](&this->sb_);
other.uses_heap_ = false;
// Bring `other`'s stuff to `*this`
this->ptr_ = ptr;
this->uses_heap_ = true;
} else {
// Move `other` into temporary local storage, destructively.
SBStorage tmp;
other_vtable["move-construct"_s](&tmp, &other.sb_);
other_vtable["destruct"_s](&other.sb_);
// Move `*this` into `other`, destructively.
this_vtable["move-construct"_s](&other.sb_, &this->sb_);
this_vtable["destruct"_s](&this->sb_);
// Now, bring `tmp` into `*this`, destructively.
other_vtable["move-construct"_s](&this->sb_, &tmp);
other_vtable["destruct"_s](&tmp);
}
}
}
template <typename VTable>
void destruct(VTable const& vtable) {
if (uses_heap()) {
// If we've been moved from, don't do anything.
if (ptr_ == nullptr)
return;
vtable["destruct"_s](ptr_);
std::free(ptr_);
} else {
vtable["destruct"_s](&sb_);
}
}
template <typename T = void>
T* get() {
return static_cast<T*>(uses_heap() ? ptr_ : &sb_);
}
template <typename T = void>
T const* get() const {
return static_cast<T const*>(uses_heap() ? ptr_ : &sb_);
}
private:
bool uses_heap() const { return uses_heap_; }
};
// Class implementing storage on the heap. Just like the `sbo_storage`, it
// only handles allocation and deallocation; construction and destruction
// must be handled externally.
struct remote_storage {
remote_storage() = delete;
remote_storage(remote_storage const&) = delete;
remote_storage(remote_storage&&) = delete;
remote_storage& operator=(remote_storage&&) = delete;
remote_storage& operator=(remote_storage const&) = delete;
template <typename T, typename RawT = std::decay_t<T>>
explicit remote_storage(T&& t)
: ptr_{std::malloc(sizeof(RawT))}
{
// TODO: That's not a really nice way to handle this
assert(ptr_ != nullptr && "std::malloc failed, we're doomed");
new (ptr_) RawT(std::forward<T>(t));
}
template <typename VTable>
remote_storage(remote_storage const& other, VTable const& vtable)
: ptr_{std::malloc(vtable["storage_info"_s]().size)}
{
// TODO: That's not a really nice way to handle this
assert(ptr_ != nullptr && "std::malloc failed, we're doomed");
vtable["copy-construct"_s](this->get(), other.get());
}
template <typename VTable>
remote_storage(remote_storage&& other, VTable const&)
: ptr_{other.ptr_}
{
other.ptr_ = nullptr;
}
template <typename MyVTable, typename OtherVTable>
void swap(MyVTable const&, remote_storage& other, OtherVTable const&) {
std::swap(this->ptr_, other.ptr_);
}
template <typename VTable>
void destruct(VTable const& vtable) {
// If we've been moved from, don't do anything.
if (ptr_ == nullptr)
return;
vtable["destruct"_s](ptr_);
std::free(ptr_);
}
template <typename T = void>
T* get() {
return static_cast<T*>(ptr_);
}
template <typename T = void>
T const* get() const {
return static_cast<T const*>(ptr_);
}
static constexpr bool can_store(dyno::storage_info) {
return true;
}
private:
void* ptr_;
};
// Class implementing shared remote storage.
//
// This is basically the same as using a `std::shared_ptr` to store the
// polymorphic object.
//
// TODO:
// - Using `std::shared_ptr` in the implementation is suboptimal, because it
// reimplements type erasure for the deleter, but we could really reuse our
// vtable instead.
// - For remote storage policies, should it be possible to specify whether the
// pointed-to storage is const?
struct shared_remote_storage {
shared_remote_storage() = delete;
shared_remote_storage(shared_remote_storage const&) = delete;
shared_remote_storage(shared_remote_storage&&) = delete;
shared_remote_storage& operator=(shared_remote_storage&&) = delete;
shared_remote_storage& operator=(shared_remote_storage const&) = delete;
template <typename T, typename RawT = std::decay_t<T>>
explicit shared_remote_storage(T&& t)
: ptr_{std::make_shared<RawT>(std::forward<T>(t))}
{ }
template <typename VTable>
shared_remote_storage(shared_remote_storage const& other, VTable const&)
: ptr_{other.ptr_}
{ }
template <typename VTable>
shared_remote_storage(shared_remote_storage&& other, VTable const&)
: ptr_{std::move(other.ptr_)}
{ }
template <typename MyVTable, typename OtherVTable>
void swap(MyVTable const&, shared_remote_storage& other, OtherVTable const&) {
using std::swap;
swap(this->ptr_, other.ptr_);
}
template <typename VTable>
void destruct(VTable const&) {
ptr_.reset();
}
template <typename T = void>
T* get() {
return static_cast<T*>(ptr_.get());
}
template <typename T = void>
T const* get() const {
return static_cast<T const*>(ptr_.get());
}
static constexpr bool can_store(dyno::storage_info) {
return true;
}
private:
std::shared_ptr<void> ptr_;
};
// Class implementing unconditional storage in a local buffer.
//
// This is like a small buffer optimization, except the behavior is undefined
// when the object can't fit inside the buffer. Since we know the object always
// sits inside the local buffer, we can get rid of a branch when accessing the
// object.
template <std::size_t Size, std::size_t Align = static_cast<std::size_t>(-1)>
class local_storage {
static constexpr std::size_t SBAlign = Align == static_cast<std::size_t>(-1)
? alignof(std::aligned_storage_t<Size>)
: Align;
using SBStorage = std::aligned_storage_t<Size, SBAlign>;
SBStorage buffer_;
public:
local_storage() = delete;
local_storage(local_storage const&) = delete;
local_storage(local_storage&&) = delete;
local_storage& operator=(local_storage&&) = delete;
local_storage& operator=(local_storage const&) = delete;
static constexpr bool can_store(dyno::storage_info info) {
return info.size <= sizeof(SBStorage) && alignof(SBStorage) % info.alignment == 0;
}
template <typename T, typename RawT = std::decay_t<T>>
explicit local_storage(T&& t) {
// TODO: We could also construct the object at an aligned address within
// the buffer, which would require computing the right address everytime
// we access the buffer as a T, but would allow more Ts to fit inside it.
static_assert(can_store(dyno::storage_info_for<RawT>),
"dyno::local_storage: Trying to construct from an object that won't fit "
"in the local storage.");
new (&buffer_) RawT(std::forward<T>(t));
}
template <typename VTable>
local_storage(local_storage const& other, VTable const& vtable) {
assert(can_store(vtable["storage_info"_s]()) &&
"dyno::local_storage: Trying to copy-construct using a vtable that "
"describes an object that won't fit in the storage.");
vtable["copy-construct"_s](this->get(), other.get());
}
template <typename VTable>
local_storage(local_storage&& other, VTable const& vtable) {
assert(can_store(vtable["storage_info"_s]()) &&
"dyno::local_storage: Trying to move-construct using a vtable that "
"describes an object that won't fit in the storage.");
vtable["move-construct"_s](this->get(), other.get());
}
template <typename MyVTable, typename OtherVTable>
void swap(MyVTable const& this_vtable, local_storage& other, OtherVTable const& other_vtable) {
if (this == &other)
return;
// Move `other` into temporary local storage, destructively.
SBStorage tmp;
other_vtable["move-construct"_s](&tmp, &other.buffer_);
other_vtable["destruct"_s](&other.buffer_);
// Move `*this` into `other`, destructively.
this_vtable["move-construct"_s](&other.buffer_, &this->buffer_);
this_vtable["destruct"_s](&this->buffer_);
// Now, bring `tmp` into `*this`, destructively.
other_vtable["move-construct"_s](&this->buffer_, &tmp);
other_vtable["destruct"_s](&tmp);
}
template <typename VTable>
void destruct(VTable const& vtable) {
vtable["destruct"_s](&buffer_);
}
template <typename T = void>
T* get() {
return static_cast<T*>(static_cast<void*>(&buffer_));
}
template <typename T = void>
T const* get() const {
return static_cast<T const*>(static_cast<void const*>(&buffer_));
}
};
// Class implementing a non-owning polymorphic reference. Unlike the other
// storage classes, this one does not own the object it holds, and hence it
// does not construct or destruct it. The referenced object must outlive the
// polymorphic storage that references it, otherwise the behavior is undefined.
struct non_owning_storage {
non_owning_storage() = delete;
non_owning_storage(non_owning_storage const&) = delete;
non_owning_storage(non_owning_storage&&) = delete;
non_owning_storage& operator=(non_owning_storage&&) = delete;
non_owning_storage& operator=(non_owning_storage const&) = delete;
template <typename T>
explicit non_owning_storage(T& t)
: ptr_{&t}
{ }
template <typename VTable>
non_owning_storage(non_owning_storage const& other, VTable const&)
: ptr_{other.ptr_}
{ }
template <typename VTable>
non_owning_storage(non_owning_storage&& other, VTable const&)
: ptr_{other.ptr_}
{ }
template <typename MyVTable, typename OtherVTable>
void swap(MyVTable const&, non_owning_storage& other, OtherVTable const&) {
std::swap(this->ptr_, other.ptr_);
}
template <typename VTable>
void destruct(VTable const&) { }
template <typename T = void>
T* get() {
return static_cast<T*>(ptr_);
}
template <typename T = void>
T const* get() const {
return static_cast<T const*>(ptr_);
}
static constexpr bool can_store(dyno::storage_info) {
return true;
}
private:
void* ptr_;
};
// Class implementing polymorphic storage with a primary storage and a
// fallback one.
//
// When the primary storage can be used to store a type, it is used. When it
// can't, however, the secondary storage is used instead. This can be used
// to implement a small buffer optimization, by using `dyno::local_storage` as
// the primary storage, and `dyno::remote_storage` as the secondary.
//
// TODO:
// - Consider implementing this by storing a pointer to the active object.
// - Alternatively, if we had a way to access the vtable here, we could
// retrieve the size of the type from it and know whether we are in the
// primary or the secondary storage like that.
// - If we were to store the vtable inside storage classes, we could also
// encode which storage is active using the low bits of the pointer.
// - Technically, this could be used to implement `sbo_storage`. However,
// benchmarks show that `sbo_storage` is significantly more efficient.
// We should try to optimize `fallback_storage` so that it can replace sbo.
template <typename First, typename Second>
class fallback_storage {
union { First first_; Second second_; };
bool in_first_;
bool in_first() const { return in_first_; }
public:
fallback_storage() = delete;
fallback_storage(fallback_storage const&) = delete;
fallback_storage(fallback_storage&&) = delete;
fallback_storage& operator=(fallback_storage&&) = delete;
fallback_storage& operator=(fallback_storage const&) = delete;
template <typename T, typename RawT = std::decay_t<T>,
typename = std::enable_if_t<First::can_store(dyno::storage_info_for<RawT>)>>
explicit fallback_storage(T&& t) : in_first_{true}
{ new (&first_) First{std::forward<T>(t)}; }
template <typename T, typename RawT = std::decay_t<T>, typename = void,
typename = std::enable_if_t<!First::can_store(dyno::storage_info_for<RawT>)>>
explicit fallback_storage(T&& t) : in_first_{false} {
static_assert(can_store(dyno::storage_info_for<RawT>),
"dyno::fallback_storage<First, Second>: Trying to construct from a type "
"that can neither be stored in the primary nor in the secondary storage.");
new (&second_) Second{std::forward<T>(t)};
}
template <typename VTable>
fallback_storage(fallback_storage const& other, VTable const& vtable)
: in_first_{other.in_first_}
{
if (in_first())
new (&first_) First{other.first_, vtable};
else
new (&second_) Second{other.second_, vtable};
}
template <typename VTable>
fallback_storage(fallback_storage&& other, VTable const& vtable)
: in_first_{other.in_first_}
{
if (in_first())
new (&first_) First{std::move(other.first_), vtable};
else
new (&second_) Second{std::move(other.second_), vtable};
}
// TODO: With a destructive move, we could avoid all the calls to `destruct` below.
template <typename MyVTable, typename OtherVTable>
void swap(MyVTable const& this_vtable, fallback_storage& other, OtherVTable const& other_vtable) {
if (this->in_first()) {
if (other.in_first()) {
this->first_.swap(this_vtable, other.first_, other_vtable);
} else {
// Move `this->first` into a temporary, destructively.
First tmp{std::move(this->first_), this_vtable};
this->first_.destruct(this_vtable);
this->first_.~First();
// Move `other.second` into `this->second`, destructively.
new (&this->second_) Second{std::move(other.second_), other_vtable};
this->in_first_ = false;
other.second_.destruct(other_vtable);
other.second_.~Second();
// Move `tmp` into `other.first`.
new (&other.first_) First{std::move(tmp), this_vtable};
other.in_first_ = true;
}
} else {
if (other.in_first()) {
// Move `this->second` into a temporary, destructively.
Second tmp{std::move(this->second_), this_vtable};
this->second_.destruct(this_vtable);
this->second_.~Second();
// Move `other.first` into `this->first`, destructively.
new (&this->first_) First{std::move(other.first_), other_vtable};
this->in_first_ = true;
other.first_.destruct(other_vtable);
other.first_.~First();
// Move `tmp` into `other.second`.
new (&other.second_) Second{std::move(tmp), this_vtable};
other.in_first_ = false;
} else {
this->second_.swap(this_vtable, other.second_, other_vtable);
}
}
}
template <typename VTable>
void destruct(VTable const& vtable) {
if (in_first())
first_.destruct(vtable);
else
second_.destruct(vtable);
}
template <typename T = void>
T* get() {
return static_cast<T*>(in_first() ? first_.template get<T>()
: second_.template get<T>());
}
template <typename T = void>
T const* get() const {
return static_cast<T const*>(in_first() ? first_.template get<T>()
: second_.template get<T>());
}
static constexpr bool can_store(dyno::storage_info info) {
return First::can_store(info) || Second::can_store(info);
}
};
} // end namespace dyno
#endif // DYNO_STORAGE_HPP

View File

@ -1,415 +0,0 @@
// Copyright Louis Dionne 2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
#ifndef DYNO_VTABLE_HPP
#define DYNO_VTABLE_HPP
#include <dyno/concept.hpp>
#include <dyno/detail/erase_function.hpp>
#include <dyno/detail/erase_signature.hpp>
#include <boost/hana/at_key.hpp>
#include <boost/hana/basic_tuple.hpp>
#include <boost/hana/bool.hpp>
#include <boost/hana/contains.hpp>
#include <boost/hana/core/to.hpp>
#include <boost/hana/difference.hpp>
#include <boost/hana/first.hpp>
#include <boost/hana/fold_left.hpp>
#include <boost/hana/for_each.hpp>
#include <boost/hana/is_subset.hpp>
#include <boost/hana/length.hpp>
#include <boost/hana/map.hpp>
#include <boost/hana/or.hpp>
#include <boost/hana/pair.hpp>
#include <boost/hana/second.hpp>
#include <boost/hana/set.hpp>
#include <boost/hana/type.hpp>
#include <boost/hana/unpack.hpp>
#include <type_traits>
#include <utility>
namespace dyno {
// concept VTable
//
// A VTable is a mapping from statically-known function names to
// dynamically-known function pointers. In some sense, a vtable is
// a type-erased concept map.
//
// In addition to being `Swappable`, a type `Table` satisfying the `VTable`
// concept must provide the following functions as part of its interface:
//
// template <typename ConceptMap> explicit Table(ConceptMap);
// Semantics: Construct a vtable with the functions from a concept map.
// Note that the actual instance of the concept map being passed
// as a parameter is not required to be used; if that is enough
// for the purpose of the vtable, any instance of that concept
// map sharing the same type may be used.
//
// Note: Concept maps have unique types, so this wording is only
// a way of ensuring we can statically construct a vtable
// from just the type of that concept map.
//
// template <typename Name> constexpr auto contains(Name) const;
// Semantics: Return whether the vtable contains a function with the given
// name, which must be a compile-time string.
//
// template <typename Name> constexpr auto operator[](Name) const;
// Semantics: Return the function with the given name in the vtable if there
// is one. The behavior when no such function exists in the vtable
// is implementation defined (in most cases that's a compile-time
// error).
//////////////////////////////////////////////////////////////////////////////
// Vtable implementations
// Class implementing a local vtable, i.e. a vtable whose storage is held
// right where the `local_vtable` is instantiated.
template <typename ...Mappings>
struct local_vtable;
template <typename ...Name, typename ...Clause>
struct local_vtable<boost::hana::pair<Name, Clause>...> {
template <typename ConceptMap>
constexpr explicit local_vtable(ConceptMap map)
: vtbl_{boost::hana::make_map(
boost::hana::make_pair(Name{}, detail::erase_function<typename Clause::type>(map[Name{}]))...
)} {
// suppress "unused" warnings for empty parameter packs
(void) map;
}
template <typename Name_>
constexpr auto contains(Name_ name) const {
return boost::hana::contains(vtbl_, name);
}
template <typename Name_>
constexpr auto operator[](Name_ name) const {
constexpr bool contains_function = decltype(contains(name))::value;
if constexpr (contains_function) {
return vtbl_[name];
} else {
static_assert(contains_function,
"dyno::local_vtable::operator[]: Request for a virtual function that is "
"not in the vtable. Was this function specified in the concept that "
"was used to instantiate this vtable? You can find the contents of the "
"vtable and the function you were trying to access in the compiler "
"error message, probably in the following format: "
"`local_vtable<CONTENTS OF VTABLE>::operator[]<FUNCTION NAME>`");
}
}
friend void swap(local_vtable& a, local_vtable& b) {
boost::hana::for_each(boost::hana::keys(a.vtbl_), [&](auto key) {
using std::swap;
swap(a.vtbl_[key], b.vtbl_[key]);
});
}
private:
boost::hana::map<
boost::hana::pair<Name, typename detail::erase_signature<typename Clause::type>::type*>...
> vtbl_;
};
namespace detail {
template <typename VTable, typename ConceptMap>
static VTable const static_vtable{ConceptMap{}};
}
// Class implementing a vtable whose storage is held remotely. This is
// basically a pointer to a static instance of the specified `VTable`.
template <typename VTable>
struct remote_vtable {
template <typename ConceptMap>
constexpr explicit remote_vtable(ConceptMap)
: vptr_{&detail::static_vtable<VTable, ConceptMap>}
{ }
template <typename Name>
constexpr auto operator[](Name name) const {
return (*vptr_)[name];
}
template <typename Name>
constexpr auto contains(Name name) const {
return vptr_->contains(name);
}
friend void swap(remote_vtable& a, remote_vtable& b) {
using std::swap;
swap(a.vptr_, b.vptr_);
}
private:
VTable const* vptr_;
};
// Class implementing a vtable that joins two other vtables.
//
// A function is first looked up in the first vtable, and in the second
// vtable if it can't be found in the first one. It is an error if a
// function is contained in both vtables, since this is most likely a
// programming error.
template <typename First, typename Second>
struct joined_vtable {
template <typename ConceptMap>
constexpr explicit joined_vtable(ConceptMap map)
: first_{map}, second_{map}
{ }
template <typename Name>
constexpr auto contains(Name name) const {
return first_.contains(name) || second_.contains(name);
}
template <typename Name>
constexpr auto operator[](Name name) const {
auto first_contains_function = first_.contains(name);
auto second_contains_function = second_.contains(name);
if constexpr (first_contains_function && second_contains_function) {
static_assert(!first_contains_function || !second_contains_function,
"dyno::joined_vtable::operator[]: Request for a virtual function that is "
"contained in both vtables of a joined vtable. Since this is most likely "
"a programming error, this is not allowed. You can find the contents of "
"the vtable and the function you were trying to access in the compiler "
"error message, probably in the following format: "
"`joined_vtable<VTABLE 1, VTABLE 2>::operator[]<FUNCTION NAME>`");
} else if constexpr (!first_contains_function && !second_contains_function) {
static_assert(first_contains_function || second_contains_function,
"dyno::joined_vtable::operator[]: Request for a virtual function that is "
"not present in any of the joined vtables. Make sure you meant to look "
"this function up, and otherwise check whether the two sub-vtables look "
"as expected. You can find the contents of the joined vtables and the "
"function you were trying to access in the compiler error message, "
"probably in the following format: "
"`joined_vtable<VTABLE 1, VTABLE 2>::operator[]<FUNCTION NAME>`");
} else if constexpr (first_contains_function) {
return first_[name];
} else {
return second_[name];
}
}
private:
First first_;
Second second_;
};
//////////////////////////////////////////////////////////////////////////////
// Selectors
template <typename ...Functions>
struct only {
template <typename All>
constexpr auto operator()(All all) const {
auto matched = boost::hana::make_set(Functions{}...);
static_assert(decltype(boost::hana::is_subset(matched, all))::value,
"dyno::only: Some functions specified in this selector are not part of "
"the concept to which the selector was applied.");
return boost::hana::make_pair(
boost::hana::difference(all, matched),
matched
);
}
};
template <typename ...Functions>
struct except {
template <typename All>
constexpr auto operator()(All all) const {
auto not_matched = boost::hana::make_set(Functions{}...);
static_assert(decltype(boost::hana::is_subset(not_matched, all))::value,
"dyno::except: Some functions specified in this selector are not part of "
"the concept to which the selector was applied.");
return boost::hana::make_pair(
not_matched,
boost::hana::difference(all, not_matched)
);
}
};
struct everything {
template <typename All>
constexpr auto operator()(All all) const {
return boost::hana::make_pair(boost::hana::make_set(), all);
}
};
using everything_else = everything;
namespace detail {
template <typename T>
struct is_valid_selector : boost::hana::false_ { };
template <typename ...Methods>
struct is_valid_selector<dyno::only<Methods...>>
: boost::hana::true_
{ };
template <typename ...Methods>
struct is_valid_selector<dyno::except<Methods...>>
: boost::hana::true_
{ };
template <>
struct is_valid_selector<dyno::everything>
: boost::hana::true_
{ };
} // end namespace detail
//////////////////////////////////////////////////////////////////////////////
// Vtable policies
template <typename Selector>
struct local {
static_assert(detail::is_valid_selector<Selector>::value,
"dyno::local: Provided invalid selector. Valid selectors are "
"'dyno::only<METHODS...>', 'dyno::except<METHODS...>', "
"'dyno::everything', and 'dyno::everything_else'.");
template <typename Concept, typename Functions>
static constexpr auto create(Concept, Functions functions) {
return boost::hana::unpack(functions, [](auto ...f) {
using VTable = dyno::local_vtable<
boost::hana::pair<decltype(f), decltype(Concept{}.get_signature(f))>...
>;
return boost::hana::basic_type<VTable>{};
});
}
Selector selector;
};
template <typename Selector>
struct remote {
static_assert(detail::is_valid_selector<Selector>::value,
"dyno::remote: Provided invalid selector. Valid selectors are "
"'dyno::only<METHODS...>', 'dyno::except<METHODS...>', "
"'dyno::everything', and 'dyno::everything_else'.");
template <typename Concept, typename Functions>
static constexpr auto create(Concept, Functions functions) {
return boost::hana::template_<dyno::remote_vtable>(
dyno::local<Selector>::create(Concept{}, functions)
);
}
Selector selector;
};
namespace detail {
// Returns whether a vtable is empty, such that we can completely skip it
// when composing policies below.
template <typename VTable>
struct is_empty_vtable : boost::hana::false_ { };
template <>
struct is_empty_vtable<dyno::local_vtable<>> : boost::hana::true_ { };
} // end namespace detail
template <typename Concept, typename Policies>
constexpr auto generate_vtable(Policies policies) {
auto functions = boost::hana::to_set(dyno::clause_names(Concept{}));
auto state = boost::hana::make_pair(functions, boost::hana::basic_type<dyno::local_vtable<>>{});
auto result = boost::hana::fold_left(policies, state, [](auto state, auto policy) {
auto functions = boost::hana::first(state);
auto vtable = boost::hana::second(state);
auto selector_split = policy.selector(functions);
auto remaining = boost::hana::first(selector_split);
auto matched = boost::hana::second(selector_split);
if constexpr (detail::is_empty_vtable<typename decltype(vtable)::type>{}) {
auto new_vtable = decltype(policy.create(Concept{}, matched)){};
return boost::hana::make_pair(remaining, new_vtable);
} else {
auto new_vtable = boost::hana::basic_type<
dyno::joined_vtable<
typename decltype(vtable)::type,
typename decltype(policy.create(Concept{}, matched))::type
>
>{};
return boost::hana::make_pair(remaining, new_vtable);
}
});
constexpr bool all_functions_were_taken = decltype(boost::hana::length(boost::hana::first(result)))::value == 0;
static_assert(all_functions_were_taken,
"dyno::vtable: The policies specified in the vtable did not fully cover all "
"the functions provided by the concept. Some functions were not mapped to "
"any vtable, which is an error");
return boost::hana::second(result);
}
// Policy-based interface for defining vtables.
//
// This type does not model the `VTable` concept itself; instead, it is used
// to generate a type that models that concept.
//
// A `vtable` is parameterized on one or more policies, which specify how
// the vtable is implemented under the hood. Some policies can be further
// parameterized using a `Selector`, in which case the functions specified
// by the `Selector` are the ones to which the policy applies. Policies
// provided by the library are:
//
// dyno::remote<Selector>
// All functions selected by `Selector` will be stored in a remote vtable.
// The vtable object is just a pointer to an actual vtable, and each access
// to the vtable requires one indirection. In vanilla C++, this is the usual
// vtable implementation.
//
// dyno::local<Selector>
// All functions selected by `Selector` will be stored in a local vtable.
// The vtable object will actually contain function pointers for all the
// selected functions. When accessing a virtual function, no additional
// indirection is required (compared to a vtable stored remotely), at the
// cost of space inside the vtable object.
//
//
// A selector is a type that selects a subset of functions defined by a concept.
// Selectors are used to pick which policy applies to which functions when
// defining a `vtable`. For example, one might want to define a vtable where
// all the functions except one (say `"f"`) are stored remotely, with `"f"`
// being stored locally. This can be achieved by using the `dyno::remote` policy
// with a selector that picks all functions except `"f"`, and the `dyno::local`
// policy with a selector that picks everything (all that remains). Note that
// when multiple selectors are specified, functions picked by earlier selectors
// will effectively be removed from the concept for later selectors, which
// supports this use case. Otherwise, one would have to specify that the
// `dyno::local` contains everything except the `"f"` function, which is
// cumbersome. Selectors provided by the library are:
//
// dyno::only<functions...>
// Picks only the specified functions from a concept. `functions` must be
// compile-time strings, such as `dyno::only<decltype("foo"_s), decltype("bar"_s)>`.
//
// dyno::except<functions...>
// Picks all but the specified functions from a concept. `functions` must
// be compile-time strings, such as `dyno::except<decltype("foo"_s), decltype("bar"_s)>`.
//
// dyno::everything
// Picks all the functions from a concept.
//
// dyno::everything_else
// Equivalent to `dyno::everything`, but prettier to read when other
// policies are used before it.
template <typename ...Policies>
struct vtable {
template <typename Concept>
using apply = typename decltype(
dyno::generate_vtable<Concept>(boost::hana::basic_tuple<Policies...>{})
)::type;
};
} // end namespace dyno
#endif // DYNO_VTABLE_HPP

View File

@ -358,7 +358,7 @@ create unique index "uk_parameter_name" on "parameter"("name");
create trigger update_parameter_last_update before update on "parameter" for each row execute procedure last_update_column();
comment on table "parameter"
is 'Parameter data';
comment on table "parameter"."name"
comment on column "parameter"."name"
is 'name of parameter e.g. Max power';
comment on column "parameter"."unit_id"
is 'id of assigned unit (for range types, unit/list types indicated type of single value)';

View File

@ -18,7 +18,7 @@ class PgItemsRepository : public ItemsRepository {
public:
PgItemsRepository(db::PgConnection & db, User * owner);
std::unique_ptr< Item > create(item::Identyfier id, Values values, std::optional< item::Foto >) const override;
void create(item::Identyfier id, Values values, std::optional< item::Foto >) const override;
std::unique_ptr< Items > search(const ItemQueryFilters & filer) const override;

View File

@ -11,13 +11,13 @@
#include <eedb/db/pg/model/unit.h>
#include <eedb/db/pg/model/user_audit.h>
static constexpr eedb::auth_identity t_auth_identity;
static constexpr eedb::auth_info t_auth_info;
static constexpr eedb::auth_token t_auth_token;
static constexpr eedb::category t_category;
static constexpr eedb::group t_group;
static constexpr eedb::parameter t_parameter;
static constexpr eedb::stat t_stat;
static constexpr eedb::system_info t_system_info;
static constexpr eedb::unit t_unit;
static constexpr eedb::user_audit t_user_audit;
static constexpr eedb::db::auth_identity t_auth_identity;
static constexpr eedb::db::auth_info t_auth_info;
static constexpr eedb::db::auth_token t_auth_token;
static constexpr eedb::db::category t_category;
static constexpr eedb::db::group t_group;
static constexpr eedb::db::parameter t_parameter;
static constexpr eedb::db::stat t_stat;
static constexpr eedb::db::system_info t_system_info;
static constexpr eedb::db::unit t_unit;
static constexpr eedb::db::user_audit t_user_audit;

View File

@ -1,11 +1,10 @@
#ifndef EEDB_AUTH_IDENTITY_H
#define EEDB_AUTH_IDENTITY_H
#pragma once
#include <sqlpp11/table.h>
#include <sqlpp11/char_sequence.h>
#include <sqlpp11/column_types.h>
namespace eedb {
namespace eedb::db {
namespace auth_identity_ {
@ -88,5 +87,3 @@ namespace eedb {
};
};
}
#endif

View File

@ -1,11 +1,10 @@
#ifndef EEDB_AUTH_INFO_H
#define EEDB_AUTH_INFO_H
#pragma once
#include <sqlpp11/table.h>
#include <sqlpp11/char_sequence.h>
#include <sqlpp11/column_types.h>
namespace eedb {
namespace eedb::db {
namespace auth_info_ {
@ -184,5 +183,3 @@ namespace eedb {
};
};
}
#endif

View File

@ -1,11 +1,10 @@
#ifndef EEDB_AUTH_TOKEN_H
#define EEDB_AUTH_TOKEN_H
#pragma once
#include <sqlpp11/table.h>
#include <sqlpp11/char_sequence.h>
#include <sqlpp11/column_types.h>
namespace eedb {
namespace eedb::db {
namespace auth_token_ {
@ -105,4 +104,3 @@ namespace eedb {
};
}
#endif

View File

@ -1,11 +1,10 @@
#ifndef EEDB_CATEGORY_H
#define EEDB_CATEGORY_H
#pragma once
#include <sqlpp11/table.h>
#include <sqlpp11/char_sequence.h>
#include <sqlpp11/column_types.h>
namespace eedb {
namespace eedb::db {
namespace category_ {
@ -217,4 +216,3 @@ namespace eedb {
};
}
#endif

View File

@ -1,11 +1,11 @@
#ifndef EEDB_CATEGORY_ITEMS_H
#define EEDB_CATEGORY_ITEMS_H
#ifndef EEDB::DB_CATEGORY_ITEMS_H
#define EEDB::DB_CATEGORY_ITEMS_H
#include <sqlpp11/table.h>
#include <sqlpp11/char_sequence.h>
#include <sqlpp11/column_types.h>
namespace eedb {
namespace eedb::db {
namespace category_items_ {

View File

@ -1,11 +1,9 @@
#ifndef EEDB_GROUP_H
#define EEDB_GROUP_H
#pragma once
#include <sqlpp11/table.h>
#include <sqlpp11/char_sequence.h>
#include <sqlpp11/column_types.h>
namespace eedb {
namespace eedb::db {
namespace group_ {
@ -104,5 +102,3 @@ namespace eedb {
};
};
}
#endif

View File

@ -1,11 +1,11 @@
#ifndef EEDB_ITEM_H
#define EEDB_ITEM_H
#ifndef EEDB::DB_ITEM_H
#define EEDB::DB_ITEM_H
#include <sqlpp11/table.h>
#include <sqlpp11/char_sequence.h>
#include <sqlpp11/column_types.h>
namespace eedb {
namespace eedb::db {
namespace item_ {

View File

@ -1,11 +1,10 @@
#ifndef EEDB_PARAMETER_H
#define EEDB_PARAMETER_H
#pragma once
#include <sqlpp11/table.h>
#include <sqlpp11/char_sequence.h>
#include <sqlpp11/column_types.h>
namespace eedb {
namespace eedb::db {
namespace parameter_ {
@ -184,5 +183,3 @@ namespace eedb {
};
};
}
#endif

View File

@ -1,11 +1,10 @@
#ifndef EEDB_STAT_H
#define EEDB_STAT_H
#pragma once
#include <sqlpp11/table.h>
#include <sqlpp11/char_sequence.h>
#include <sqlpp11/column_types.h>
namespace eedb {
namespace eedb::db {
namespace stat_ {
@ -153,4 +152,3 @@ namespace eedb {
};
}
#endif

View File

@ -1,11 +1,10 @@
#ifndef EEDB_SYSTEM_INFO_H
#define EEDB_SYSTEM_INFO_H
#pragma once
#include <sqlpp11/table.h>
#include <sqlpp11/char_sequence.h>
#include <sqlpp11/column_types.h>
namespace eedb {
namespace eedb::db {
namespace system_info_ {
@ -88,5 +87,3 @@ namespace eedb {
};
};
}
#endif

View File

@ -1,204 +1,249 @@
#ifndef EEDB_UNIT_H
#define EEDB_UNIT_H
#pragma once
#include <sqlpp11/table.h>
#include <sqlpp11/char_sequence.h>
#include <sqlpp11/column_types.h>
#include <sqlpp11/table.h>
namespace eedb {
namespace eedb::db {
namespace unit_ {
namespace unit_ {
struct Id {
struct _alias_t {
static constexpr const char _literal[] = R"("id")";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template<typename T>
struct _member_t {
T id;
T &operator()() { return id; }
const T &operator()() const { return id; }
};
};
struct Id {
struct _alias_t {
static constexpr const char _literal[] = R"("id")";
using _name_t = sqlpp::make_char_sequence< sizeof(_literal), _literal >;
template < typename T >
struct _member_t {
T id;
T & operator()() {
return id;
}
const T & operator()() const {
return id;
}
};
};
using _traits = ::sqlpp::make_traits<::sqlpp::integer>;
};
using _traits = ::sqlpp::make_traits<::sqlpp::integer >;
};
struct Owner {
struct _alias_t {
static constexpr const char _literal[] = R"("owner")";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template<typename T>
struct _member_t {
T owner;
T &operator()() { return owner; }
const T &operator()() const { return owner; }
};
};
struct Owner {
struct _alias_t {
static constexpr const char _literal[] = R"("owner")";
using _name_t = sqlpp::make_char_sequence< sizeof(_literal), _literal >;
template < typename T >
struct _member_t {
T owner;
T & operator()() {
return owner;
}
const T & operator()() const {
return owner;
}
};
};
using _traits = ::sqlpp::make_traits<::sqlpp::integer>;
};
using _traits = ::sqlpp::make_traits<::sqlpp::integer >;
};
struct Group {
struct _alias_t {
static constexpr const char _literal[] = R"("group")";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template<typename T>
struct _member_t {
T group;
T &operator()() { return group; }
const T &operator()() const { return group; }
};
};
struct Group {
struct _alias_t {
static constexpr const char _literal[] = R"("group")";
using _name_t = sqlpp::make_char_sequence< sizeof(_literal), _literal >;
template < typename T >
struct _member_t {
T group;
T & operator()() {
return group;
}
const T & operator()() const {
return group;
}
};
};
using _traits = ::sqlpp::make_traits<::sqlpp::integer>;
};
using _traits = ::sqlpp::make_traits<::sqlpp::integer >;
};
struct Unixperms {
struct _alias_t {
static constexpr const char _literal[] = R"("unixperms")";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template<typename T>
struct _member_t {
T unixperms;
T &operator()() { return unixperms; }
const T &operator()() const { return unixperms; }
};
};
struct Unixperms {
struct _alias_t {
static constexpr const char _literal[] = R"("unixperms")";
using _name_t = sqlpp::make_char_sequence< sizeof(_literal), _literal >;
template < typename T >
struct _member_t {
T unixperms;
T & operator()() {
return unixperms;
}
const T & operator()() const {
return unixperms;
}
};
};
using _traits = ::sqlpp::make_traits<::sqlpp::integer>;
};
using _traits = ::sqlpp::make_traits<::sqlpp::integer >;
};
struct Status {
struct _alias_t {
static constexpr const char _literal[] = R"("status")";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template<typename T>
struct _member_t {
T status;
T &operator()() { return status; }
const T &operator()() const { return status; }
};
};
struct Status {
struct _alias_t {
static constexpr const char _literal[] = R"("status")";
using _name_t = sqlpp::make_char_sequence< sizeof(_literal), _literal >;
template < typename T >
struct _member_t {
T status;
T & operator()() {
return status;
}
const T & operator()() const {
return status;
}
};
};
using _traits = ::sqlpp::make_traits<::sqlpp::integer>;
};
using _traits = ::sqlpp::make_traits<::sqlpp::integer >;
};
struct Name {
struct _alias_t {
static constexpr const char _literal[] = R"("name")";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template<typename T>
struct _member_t {
T name;
T &operator()() { return name; }
const T &operator()() const { return name; }
};
};
struct Name {
struct _alias_t {
static constexpr const char _literal[] = R"("name")";
using _name_t = sqlpp::make_char_sequence< sizeof(_literal), _literal >;
template < typename T >
struct _member_t {
T name;
T & operator()() {
return name;
}
const T & operator()() const {
return name;
}
};
};
using _traits = ::sqlpp::make_traits<::sqlpp::text, sqlpp::tag::require_insert>;
};
using _traits = ::sqlpp::make_traits<::sqlpp::text, sqlpp::tag::require_insert >;
};
struct Created {
struct _alias_t {
static constexpr const char _literal[] = R"("created")";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template<typename T>
struct _member_t {
T created;
T &operator()() { return created; }
const T &operator()() const { return created; }
};
};
struct Created {
struct _alias_t {
static constexpr const char _literal[] = R"("created")";
using _name_t = sqlpp::make_char_sequence< sizeof(_literal), _literal >;
template < typename T >
struct _member_t {
T created;
T & operator()() {
return created;
}
const T & operator()() const {
return created;
}
};
};
using _traits = ::sqlpp::make_traits<::sqlpp::time_point>;
};
using _traits = ::sqlpp::make_traits<::sqlpp::time_point >;
};
struct Updated {
struct _alias_t {
static constexpr const char _literal[] = R"("updated")";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template<typename T>
struct _member_t {
T updated;
T &operator()() { return updated; }
const T &operator()() const { return updated; }
};
};
struct Updated {
struct _alias_t {
static constexpr const char _literal[] = R"("updated")";
using _name_t = sqlpp::make_char_sequence< sizeof(_literal), _literal >;
template < typename T >
struct _member_t {
T updated;
T & operator()() {
return updated;
}
const T & operator()() const {
return updated;
}
};
};
using _traits = ::sqlpp::make_traits<::sqlpp::time_point, sqlpp::tag::can_be_null>;
};
using _traits = ::sqlpp::make_traits<::sqlpp::time_point, sqlpp::tag::can_be_null >;
};
struct Symbol {
struct _alias_t {
static constexpr const char _literal[] = R"("symbol")";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template<typename T>
struct _member_t {
T symbol;
T &operator()() { return symbol; }
const T &operator()() const { return symbol; }
};
};
struct Symbol {
struct _alias_t {
static constexpr const char _literal[] = R"("symbol")";
using _name_t = sqlpp::make_char_sequence< sizeof(_literal), _literal >;
template < typename T >
struct _member_t {
T symbol;
T & operator()() {
return symbol;
}
const T & operator()() const {
return symbol;
}
};
};
using _traits = ::sqlpp::make_traits<::sqlpp::text, sqlpp::tag::require_insert>;
};
using _traits = ::sqlpp::make_traits<::sqlpp::text, sqlpp::tag::require_insert >;
};
struct Description {
struct _alias_t {
static constexpr const char _literal[] = R"("description")";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template<typename T>
struct _member_t {
T description;
T &operator()() { return description; }
const T &operator()() const { return description; }
};
};
struct Description {
struct _alias_t {
static constexpr const char _literal[] = R"("description")";
using _name_t = sqlpp::make_char_sequence< sizeof(_literal), _literal >;
template < typename T >
struct _member_t {
T description;
T & operator()() {
return description;
}
const T & operator()() const {
return description;
}
};
};
using _traits = ::sqlpp::make_traits<::sqlpp::text, sqlpp::tag::can_be_null>;
};
using _traits = ::sqlpp::make_traits<::sqlpp::text, sqlpp::tag::can_be_null >;
};
struct Dimension_symbol {
struct _alias_t {
static constexpr const char _literal[] = R"("dimension_symbol")";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template<typename T>
struct _member_t {
T dimension_symbol;
T &operator()() { return dimension_symbol; }
const T &operator()() const { return dimension_symbol; }
};
};
struct Dimension_symbol {
struct _alias_t {
static constexpr const char _literal[] = R"("dimension_symbol")";
using _name_t = sqlpp::make_char_sequence< sizeof(_literal), _literal >;
template < typename T >
struct _member_t {
T dimension_symbol;
T & operator()() {
return dimension_symbol;
}
const T & operator()() const {
return dimension_symbol;
}
};
};
using _traits = ::sqlpp::make_traits<::sqlpp::text, sqlpp::tag::can_be_null>;
};
}
using _traits = ::sqlpp::make_traits<::sqlpp::text, sqlpp::tag::can_be_null >;
};
} // namespace unit_
struct unit : sqlpp::table_t<unit,
unit_::Id,
unit_::Owner,
unit_::Group,
unit_::Unixperms,
unit_::Status,
unit_::Name,
unit_::Created,
unit_::Updated,
unit_::Symbol,
unit_::Description,
unit_::Dimension_symbol> {
using _value_type = sqlpp::no_value_t;
struct _alias_t {
static constexpr const char _literal[] = R"("unit")";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template<typename T>
struct _member_t {
T unit;
T &operator()() { return unit; }
const T &operator()() const { return unit; }
};
};
};
}
#endif
struct unit : sqlpp::table_t< unit,
unit_::Id,
unit_::Owner,
unit_::Group,
unit_::Unixperms,
unit_::Status,
unit_::Name,
unit_::Created,
unit_::Updated,
unit_::Symbol,
unit_::Description,
unit_::Dimension_symbol > {
using _value_type = sqlpp::no_value_t;
struct _alias_t {
static constexpr const char _literal[] = R"("unit")";
using _name_t = sqlpp::make_char_sequence< sizeof(_literal), _literal >;
template < typename T >
struct _member_t {
T unit;
T & operator()() {
return unit;
}
const T & operator()() const {
return unit;
}
};
};
};
} // namespace eedb::db

View File

@ -1,11 +1,10 @@
#ifndef EEDB_USER_AUDIT_H
#define EEDB_USER_AUDIT_H
#pragma once
#include <sqlpp11/table.h>
#include <sqlpp11/char_sequence.h>
#include <sqlpp11/column_types.h>
namespace eedb {
namespace eedb::db {
namespace user_audit_ {
@ -105,4 +104,3 @@ namespace eedb {
};
}
#endif

View File

@ -1,11 +1,11 @@
#ifndef EEDB_USER_AUDIT_ACTION_H
#define EEDB_USER_AUDIT_ACTION_H
#ifndef EEDB::DB_USER_AUDIT_ACTION_H
#define EEDB::DB_USER_AUDIT_ACTION_H
#include <sqlpp11/table.h>
#include <sqlpp11/char_sequence.h>
#include <sqlpp11/column_types.h>
namespace eedb {
namespace eedb::db {
namespace user_audit_action_ {

View File

@ -10,6 +10,8 @@
#include <sqlpp11/postgresql/insert.h>
#include <sqlpp11/sqlpp11.h>
#include <eedb/db/User.hpp>
namespace eedb::db::pg {
class CategoryMock : public ::eedb::CategoryMock {
@ -27,7 +29,7 @@ class CategoryMock : public ::eedb::CategoryMock {
}
public:
CategoryMock(PgConnection & db) : _db{db} {}
CategoryMock(PgConnection & db, User *u) : _db{db}, _u{u} {}
void _init_simple(std::string name) {
_root_guard();
@ -37,7 +39,9 @@ class CategoryMock : public ::eedb::CategoryMock {
void _initRoot() {
using namespace testing;
_root_id = _db(sqlpp::postgresql::insert_into(t_category) //
.set(t_category.name = "root", t_category.parent_id = sqlpp::null)
.set(t_category.name = "root", //
t_category.parent_id = sqlpp::null,
t_category.owner = _u->uid())
.returning(t_category.id))
.front()
.id;
@ -45,6 +49,7 @@ class CategoryMock : public ::eedb::CategoryMock {
private:
PgConnection & _db;
User * _u{nullptr};
int64_t _root_id{0};
};

View File

@ -7,7 +7,7 @@
#include <sqlpp11/postgresql/exception.h>
#include <sqlpp11/sqlpp11.h>
constexpr eedb::auth_identity t_auth_identity;
constexpr eedb::db::auth_identity t_auth_identity;
namespace eedb {

View File

@ -11,7 +11,7 @@
#include <map>
constexpr eedb::auth_token t_auth_token;
constexpr eedb::db::auth_token t_auth_token;
namespace eedb {

View File

@ -12,14 +12,10 @@ struct PgItemsRepository::PgItemsRepositoryPriv {};
PgItemsRepository::PgItemsRepository(db::PgConnection & db, User * owner) : _priv{spimpl::make_unique_impl< PgItemsRepositoryPriv >()} {}
std::unique_ptr< Item > PgItemsRepository::create(item::Identyfier id, Values values, std::optional< item::Foto >) const {
void PgItemsRepository::create(item::Identyfier id, Values values, std::optional< item::Foto >) const {
auto attributes = nlohmann::json();
values.serialize(attributes);
std::cout<<attributes.dump(2);
return {};
}
std::unique_ptr< Items > PgItemsRepository::search(const ItemQueryFilters & filer) const {

View File

@ -14,9 +14,9 @@
#include <sqlpp11/sqlpp11.h>
constexpr eedb::auth_identity t_auth_identity;
constexpr eedb::auth_info t_auth_info;
constexpr eedb::auth_token t_auth_token;
constexpr eedb::db::auth_identity t_auth_identity;
constexpr eedb::db::auth_info t_auth_info;
constexpr eedb::db::auth_token t_auth_token;
namespace eedb {

View File

@ -14,13 +14,15 @@
class PgItemsRepositoryTest : public DbTestBase< PgItemsRepositoryTest > {
public:
PgItemsRepositoryTest() : user{db()}, category{db()}, numericParameter{db()}, textParameter{db()} {
PgItemsRepositoryTest() : user{db()}, category{db(), &user}, numericParameter{db()}, textParameter{db()}, listParameter{db()} {
using namespace testing;
db().native()->start_transaction();
user._init(); // create fake user
user._expect_call_uid();
category._init_simple("main");
numericParameter._init("numeric");
textParameter._init("text");
listParameter._init("list");
sut = std::make_unique< eedb::PgItemsRepository >(db(), &user);
}
@ -30,7 +32,14 @@ class PgItemsRepositoryTest : public DbTestBase< PgItemsRepositoryTest > {
}
auto some_val() {
using namespace std::string_literals;
return eedb::Values(eedb::Value{5, &numericParameter}, eedb::Value{"some text"s, &textParameter});
using namespace testing;
EXPECT_CALL(numericParameter, name()).WillRepeatedly(Return("numeric parameter"s));
EXPECT_CALL(textParameter, name()).WillRepeatedly(Return("text parameter"s));
EXPECT_CALL(listParameter, name()).WillRepeatedly(Return("list parameter"s));
return eedb::Values(eedb::Value{5, &numericParameter},
eedb::Value{"some text"s, &textParameter},
eedb::Value{std::vector{1.2, 3.4, 5.6, 7.8}, &listParameter});
}
~PgItemsRepositoryTest() {
@ -40,7 +49,7 @@ class PgItemsRepositoryTest : public DbTestBase< PgItemsRepositoryTest > {
protected:
eedb::db::pg::UserMock user;
eedb::db::pg::CategoryMock category;
eedb::db::pg::ParameterMock numericParameter, textParameter;
eedb::db::pg::ParameterMock numericParameter, textParameter, listParameter;
int64_t sutId;
std::unique_ptr< eedb::PgItemsRepository > sut;
@ -49,13 +58,13 @@ class PgItemsRepositoryTest : public DbTestBase< PgItemsRepositoryTest > {
template <>
std::unique_ptr< PgTestDatabasePrepare > DbTestBase< PgItemsRepositoryTest >::_test_db = {};
TEST_F(PgItemsRepositoryTest, createParameterDontfail) {
EXPECT_TRUE(sut->create(some_id(), some_val(), {}));
TEST_F(PgItemsRepositoryTest, createItemDontFail) {
EXPECT_NO_THROW(sut->create(some_id(), some_val(), {}));
}
TEST_F(PgItemsRepositoryTest, createReturnsValidParameter) {
auto item = sut->create(some_id(), some_val(), {});
ASSERT_TRUE(item);
EXPECT_EQ(item->displayName(), "AM2DM-2415SH60");
// EXPECT_EQ(item->values())
}
// TEST_F(PgItemsRepositoryTest, createReturnsValidParameter) {
// auto item = sut->create(some_id(), some_val(), {});
// ASSERT_TRUE(item);
// EXPECT_EQ(item->displayName(), "AM2DM-2415SH60");
// // EXPECT_EQ(item->values())
//}

View File

@ -98,10 +98,7 @@ class Value {
Value & operator=(Value &&) = default;
void serialize(nlohmann::json & json) const {
json["value"] = _value->serializeValue();
if(_parameter)
json["parameter"] = std::string{_parameter->name()};
json[std::string{_parameter->name()}] = _value->serializeValue();
}
nlohmann::json serialize() const {
@ -151,10 +148,8 @@ class Values {
}
bool serialize(nlohmann::json & json) const {
json["values"] = nlohmann::json::array();
for(const auto & value : *this) {
value.serialize(json);
}
std::transform(
begin(), end(), std::back_inserter(json["values"]), [](const auto & value) { return value.serialize(); });
return true;
}

View File

@ -74,9 +74,8 @@ class ItemsRepository {
* @brief create
* @param id
* @param values
* @return
*/
virtual std::unique_ptr< Item > create(item::Identyfier id, Values values, std::optional< item::Foto >) const = 0;
virtual void create(item::Identyfier id, Values values, std::optional< item::Foto >) const = 0;
/**
* @brief search

View File

@ -24,7 +24,7 @@ TEST_F(ValueTest, serializeInt) {
auto json = nlohmann::json{};
_int.serialize(json);
EXPECT_EQ(json, (nlohmann::json{{"value", 4.0}, {"parameter", "parameter name"}}));
EXPECT_EQ(json, (nlohmann::json{{"parameter name", 4.0}}));
}
TEST_F(ValueTest, serializeDouble) {
@ -34,7 +34,7 @@ TEST_F(ValueTest, serializeDouble) {
auto json = nlohmann::json{};
_double.serialize(json);
EXPECT_EQ(json, (nlohmann::json{{"value", 4.0}, {"parameter", "parameter name"}}));
EXPECT_EQ(json, (nlohmann::json{{"parameter name", 4.0}}));
}
TEST_F(ValueTest, serializeString) {
@ -44,7 +44,7 @@ TEST_F(ValueTest, serializeString) {
auto json = nlohmann::json{};
_string.serialize(json);
EXPECT_EQ(json, (nlohmann::json{{"value", "asdfghjkl"}, {"parameter", "parameter name"}}));
EXPECT_EQ(json, (nlohmann::json{{"parameter name", "asdfghjkl"}}));
}
TEST_F(ValueTest, serializeListOfInts) {
@ -52,7 +52,7 @@ TEST_F(ValueTest, serializeListOfInts) {
eedb::Value _intList{std::vector<double>{1, 2, 3, 4, 5, 6}, &parameter};
EXPECT_EQ(_intList.serialize(), (nlohmann::json{{"value", {1, 2, 3, 4, 5, 6}}, {"parameter", "parameter name"}}));
EXPECT_EQ(_intList.serialize(), (nlohmann::json{{"parameter name", {1, 2, 3, 4, 5, 6}}}));
}
TEST_F(ValueTest, serializeListOfStrings) {
@ -63,7 +63,7 @@ TEST_F(ValueTest, serializeListOfStrings) {
auto json = nlohmann::json{};
_stringList.serialize(json);
EXPECT_EQ(json, (nlohmann::json{{"value", {"a"s, "b"s, "c"s}}, {"parameter", "parameter name"}}));
EXPECT_EQ(json, (nlohmann::json{{"parameter name", {"a"s, "b"s, "c"s}}}));
}
TEST_F(ValueTest, serializeListOfRange) {
@ -73,6 +73,6 @@ TEST_F(ValueTest, serializeListOfRange) {
auto json = nlohmann::json{};
_range.serialize(json);
EXPECT_EQ(json, (nlohmann::json{{"value", {{"min", 0.001}, {"max", 1000.0}}}, {"parameter", "parameter name"}}));
EXPECT_EQ(json, (nlohmann::json{{"parameter name", {{"min", 0.001}, {"max", 1000.0}}}}));
}

View File

@ -1,4 +0,0 @@
project(eedb_tests)
add_subdirectory(utils)
add_subdirectory(unit)

View File

@ -1,19 +0,0 @@
#pragma once
#include <gmock/gmock.h>
#include <eedb/Session.hpp>
#include <eedb/db/connection.hpp>
#include <Wt/Auth/Login.h>
#include <Wt/WEnvironment.h>
namespace eedb {
class SessionMock final : public Session {
// Session interface
public:
MOCK_METHOD0(db, eedb::db::PgConnection &());
MOCK_CONST_METHOD0(enviroment, const Wt::WEnvironment &());
MOCK_METHOD0(login, Wt::Auth::Login &());
};
}

View File

@ -1,11 +0,0 @@
#include <Wt/Auth/AbstractUserDatabase.h>
class AbstracUserDatabaseMock : public Wt::Auth::AbstractUserDatabase {
// AbstractUserDatabase interface
public:
Wt::Auth::User findWithId(const std::string & id) const {}
Wt::Auth::User findWithIdentity(const std::string & provider, const Wt::WString & identity) const {}
void addIdentity(const Wt::Auth::User & user, const std::string & provider, const Wt::WString & id) {}
Wt::WString identity(const Wt::Auth::User & user, const std::string & provider) const {}
void removeIdentity(const Wt::Auth::User & user, const std::string & provider) {}
};

View File

@ -1,21 +0,0 @@
#pragma once
#include <gmock/gmock.h>
#include <eedb/widgets/AuthPage.hpp>
#include <Wt/Auth/AuthWidget.h>
namespace eedb {
class AuthPageMock final : public AuthPage {
public:
AuthPageMock() {}
public:
MOCK_METHOD0(detachWidget, std::unique_ptr< Wt::Auth::AuthWidget >());
MOCK_METHOD1(registerOnNeedVerification, void(std::function< void() > f));
MOCK_METHOD1(registerOnUserWeakLogin, void(std::function< void() > f));
MOCK_METHOD1(registerOnUserStrongLogin, void(std::function< void() > f));
MOCK_METHOD1(registerOnUserLogout, void(std::function< void() > f));
};
} // namespace eedb

View File

@ -1,12 +0,0 @@
#pragma once
#include <gmock/gmock.h>
#include <eedb/widgets/HomePage.hpp>
namespace eedb {
class HomePageMock final : public HomePage {
// HomePage interface
public:
MOCK_METHOD1(attachTo, void(Wt::WContainerWidget * parent));
};
}

View File

@ -1,17 +0,0 @@
#pragma once
#include <gmock/gmock.h>
#include <eedb/widgets/NavigationBar.hpp>
namespace eedb {
class NavigationBarMock final : public NavigationBar {
public:
NavigationBarMock() {}
public:
MOCK_METHOD1(attachTo, void(Wt::WContainerWidget * parent));
MOCK_METHOD0(detach, void());
MOCK_METHOD1(registerLogoutAction, void(std::function< void() > f));
};
}

View File

@ -1,28 +0,0 @@
cmake_minimum_required(VERSION 3.0.2)
project(Tests LANGUAGES CXX)
include_directories( ../ )
include_directories( ${eedb_app_SOURCE_DIR} )
include_directories( ${gtest_SOURCE_DIR}/include)
include_directories( ${gmock_SOURCE_DIR}/include)
#if(enable_tests)
set(TEST_EXECUTABLE_NAME eedb_test )
#add test files
file(GLOB_RECURSE TEST_FILES test_*.cpp )
file(GLOB_RECURSE MOCK_FILES ../mocks/* )
INCLUDE_DIRECTORIES(${PostgreSQL_INCLUDE_DIRS})
add_executable( ${TEST_EXECUTABLE_NAME} ${TEST_FILES} ${MOCK_FILES} )
target_link_libraries( ${TEST_EXECUTABLE_NAME} GMock::main wt wttest eedb_db eedb_auth eedb_app ${Boost_LIBRARIES} stdc++fs)
add_test( ${TEST_EXECUTABLE_NAME} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TEST_EXECUTABLE_NAME})
#endif()
if(enable_benchmarks)
add_subdirectory(benchmarks)
endif()

View File

@ -1,27 +0,0 @@
#pragma once
#include <gmock/gmock.h>
#include <Wt/Test/WTestEnvironment.h>
#include <Wt/WApplication.h>
class WidgetTest : public testing::Test {
public:
WidgetTest() : app(env) {}
template < typename T >
T * ut_find(std::string && id) {
T * item = dynamic_cast< T * >(app.findWidget(id));
_assert_nut_null(item);
return item;
}
protected:
Wt::Test::WTestEnvironment env;
Wt::WApplication app;
private:
template < typename T >
void _assert_nut_null(T * t) {
ASSERT_TRUE(t != nullptr);
}
};

View File

@ -1,11 +0,0 @@
#include <gtest/gtest.h>
int main(int argc, char * argv[]) {
// Init gtest.
testing::InitGoogleTest(&argc, argv);
// Run all tests.
int result = RUN_ALL_TESTS();
return result;
}

View File

@ -1,22 +0,0 @@
#include "WidgetTest.hpp"
#include <eedb/widgets/DefaultCategoryTree.hpp>
#include <Wt/WTreeView.h>
class CategoriesTreeTest : public WidgetTest {
public:
CategoriesTreeTest() : WidgetTest(), sut(std::make_unique< eedb::DefaultCategoriesTree >()) {
sut->attachTo(app.root());
}
auto find_tree() {
return ut_find< Wt::WTreeView >("home.categories");
}
protected:
std::unique_ptr< eedb::DefaultCategoriesTree > sut;
};
TEST_F(CategoriesTreeTest, hasElements) {
find_tree();
}

View File

@ -1,83 +0,0 @@
#include <gmock/gmock.h>
#include "utils/UniquePtrMockWrapper.hpp"
#include "mocks/SessionMock.hpp"
#include "mocks/db/UserDatabaseMock.hpp"
#include "mocks/widgets/MainPageMock.hpp"
#include <Wt/Test/WTestEnvironment.h>
#include <Wt/WApplication.h>
#include <Wt/WPopupMenu.h>
///TODO change to mocked version
#include <eedb/auth/Services.hpp>
#include <eedb/widgets/DefaultAuthPage.hpp>
using namespace testing;
class DefaultAuthPageTest : public Test {
public:
DefaultAuthPageTest() : app(env) {
createApp();
}
void createApp() {
auto services = eedb::auth::Services();
// auto session = std::make_unique< eedb::SessionMock >();
// const eedb::auth::Services & baseAuth,
// std::unique_ptr< Wt::Auth::AbstractUserDatabase > userDatabase,
// Wt::Auth::Login & session
// EXPECT_CALL(*session, login()).WillOnce(ReturnRef(login));
// homePage = std::make_unique< eedb::Home >(*session);
// sut = dynamic_cast< eedb::Home * >(homePage.get());
// EXPECT_CALL(*session, enviroment()).WillOnce(ReturnRef(env));
// sut = new eedb::AuthPageImpl(services, session.login());
sut = std::make_unique< eedb::DefaultAuthPage >(services, std::move(usedDatabaseMock), login);
}
protected:
Wt::Test::WTestEnvironment env;
Wt::Auth::Login login;
std::unique_ptr<AbstracUserDatabaseMock> usedDatabaseMock;
std::function< void() > strongLoginCallback;
Wt::WApplication app;
std::unique_ptr< eedb::DefaultAuthPage > sut;
};
TEST_F(DefaultAuthPageTest, createApp) {
auto menu = dynamic_cast< Wt::WPopupMenu * >(app.findWidget("home.navigation.session_menu_popup"));
auto menuItem = dynamic_cast< Wt::WMenuItem * >(app.findWidget("home.navigation.session_menu.logout"));
// menu->triggered().emit(menuItem);
}
// TEST(a, b) {
// auto session = std::make_unique< eedb::SessionMock >();
// EXPECT_CALL(*session, enviroment()).WillOnce(ReturnRef(env));
// sut = new eedb::EEDB(std::move(session), [this]() { return authPageFactory(); }, [this]() { return homePageFactory(); });
// // Wt::WPushButton * b = dynamic_cast< Wt::WPushButton * >(app.findWidget("startbutton"));
// // Wt::WProgressBar * bar = dynamic_cast< Wt::WProgressBar * >(app.findWidget("progress"));
// // b->clicked().emit(Wt::WMouseEvent());
// // environment.endRequest();
// // for(;;) {
// // boost::this_thread::sleep(boost::posix_time::milliseconds(50));
// // std::cerr << "Progress: " << bar->value() << std::endl;
// // if(b->isEnabled())
// // break;
// // }
// // environment.startRequest();
//}

View File

@ -1,27 +0,0 @@
#include "WidgetTest.hpp"
#include "utils/UniquePtrMockWrapper.hpp"
#include "mocks/SessionMock.hpp"
#include "mocks/widgets/NavigationBarMock.hpp"
#include <eedb/widgets/DefaultHomePage.hpp>
#include <eedb/db/connection.hpp>
#include <Wt/Auth/Login.h>
class DefaultHomePageTests : public WidgetTest {
public:
DefaultHomePageTests() : WidgetTest(), sut(std::make_unique< eedb::DefaultHomePage >(session, navigationBar.getPtr())) {
sut->attachTo(app.root());
}
protected:
eedb::SessionMock session;
UniquePtrMockWrapper< eedb::NavigationBarMock > navigationBar;
std::unique_ptr< eedb::DefaultHomePage > sut;
};
// TEST_F(HomePageTests, ctor) {}

View File

@ -1,47 +0,0 @@
#include "WidgetTest.hpp"
#include <eedb/widgets/DefaultNavigationBar.hpp>
#include <Wt/WMenuItem.h>
#include <Wt/WNavigationBar.h>
struct SimpleCallback {
MOCK_METHOD0(callback, void());
};
class DefaultNavigationBarTests : public WidgetTest {
public:
DefaultNavigationBarTests() : WidgetTest(), sut(std::make_unique< eedb::DefaultNavigationBar >()) {
sut->attachTo(app.root());
}
auto find_logoutItem() {
return ut_find< Wt::WMenuItem >("navigation_bar.session_menu.logout");
}
auto find_homeItem() {
return ut_find< Wt::WMenuItem >("navigation_bar.home_menu");
}
auto find_navigationBar() {
return ut_find< Wt::WNavigationBar >("navigation_bar");
}
protected:
std::unique_ptr< eedb::DefaultNavigationBar > sut;
};
TEST_F(DefaultNavigationBarTests, hasAllNeededElements) {
find_logoutItem();
find_homeItem();
find_navigationBar();
}
TEST_F(DefaultNavigationBarTests, loginAction) {
SimpleCallback cb;
EXPECT_CALL(cb, callback());
sut->registerLogoutAction([&]() { cb.callback(); });
auto logoutItem = find_logoutItem();
logoutItem->triggered().emit(logoutItem);
}

View File

@ -1,93 +0,0 @@
#include <gmock/gmock.h>
#include "mocks/SessionMock.hpp"
#include "mocks/widgets/AuthPageMock.hpp"
#include "mocks/widgets/MainPageMock.hpp"
#include "utils/UniquePtrMockWrapper.hpp"
#include <Wt/Auth/Login.h>
#include <Wt/Test/WTestEnvironment.h>
#include <eedb/EEDB.hpp>
#include <eedb/Session.hpp>
#include <eedb/db/connection.hpp>
#include <eedb/widgets/Theme.hpp>
#include <sqlpp11/ppgen.h>
class AuthPageFactory {
public:
MOCK_METHOD0(impl, std::unique_ptr< eedb::AuthPage >());
auto operator()() {
return impl();
}
};
class HomePageFactory {
public:
MOCK_METHOD0(impl, std::unique_ptr< eedb::HomePage >());
auto operator()() {
return impl();
}
};
using namespace testing;
class EedbApplicationTest : public Test {
public:
EedbApplicationTest() {
createApp();
}
void createApp() {
expectCreateAuthPage();
auto session = std::make_unique< eedb::SessionMock >();
EXPECT_CALL(*session, enviroment()).WillOnce(ReturnRef(env));
// sut = new eedb::EEDB(std::move(session), [this]() { return authPageFactory(); }, [this]() { return homePageFactory(); });
}
void expectCreateAuthPage() {
EXPECT_CALL(authPageFactory, impl()).WillOnce(Return(ByMove(authPage.getPtr())));
EXPECT_CALL(*authPage, registerOnNeedVerification(_)).WillOnce(SaveArg< 0 >(&_needVerificationCB));
EXPECT_CALL(*authPage, registerOnUserWeakLogin(_)).WillOnce(SaveArg< 0 >(&_weakLoginCB));
EXPECT_CALL(*authPage, registerOnUserStrongLogin(_)).WillOnce(SaveArg< 0 >(&_stringLoginCB));
EXPECT_CALL(*authPage, registerOnUserLogout(_)).WillOnce(SaveArg< 0 >(&_userLogoutCB));
}
void expectCreateHomePage() {
EXPECT_CALL(homePageFactory, impl()).WillOnce(Return(ByMove(homePage.getPtr())));
// EXPECT_CALL(*authPage, detach());
EXPECT_CALL(*homePage, attachTo(_));
}
protected:
Wt::Test::WTestEnvironment env;
std::function< void() > _stringLoginCB;
std::function< void() > _weakLoginCB;
std::function< void() > _userLogoutCB;
std::function< void() > _needVerificationCB;
UniquePtrMockWrapper< eedb::AuthPageMock > authPage;
AuthPageFactory authPageFactory;
UniquePtrMockWrapper< eedb::HomePageMock > homePage;
HomePageFactory homePageFactory;
eedb::EEDB * sut;
};
//TEST_F(EedbApplicationTest, createApp) {}
//TEST_F(EedbApplicationTest, strongLoginCreatesMainPage) {
// expectCreateHomePage();
//// _stringLoginCB();
//}
//TEST_F(EedbApplicationTest, weakLoginCreatesMainPage) {
// expectCreateHomePage();
//// _weakLoginCB();
//}

View File

@ -1 +0,0 @@
include_directories( . )