GithubHelp home page GithubHelp logo

jbcoe / value_types Goto Github PK

View Code? Open in Web Editor NEW
24.0 8.0 13.0 495 KB

value types for composite class design - with allocators

License: MIT License

C++ 87.85% Starlark 3.40% CMake 8.23% Dockerfile 0.53%

value_types's Introduction

Value types for composite class design

codecov language license issues pre-commit

This repository contains two class templates: indirect and polymorphic. Both templates are designed to be used for member data in composite types.

  • An instance of indirect<T> owns an object of class T.

  • An instance of polymorphic<T> owns an object of class T or a class derived from T.

Both classes behave as value types and allow special member functions for a class that contains them as members to be generated correctly. Our experience suggests that use of these class templates can significantly decrease the burden of writing and maintaining error-prone boilerplate code.

Standardization

We'd like to see indirect and polymorphic included in a future version of the C++ standard. Prior work on standardizing similar types, indirect_value and polymorphic_value can be found at

Design of these two types is so deeply coupled that future work will proceed in an updated paper.

Use

The indirect and polymorphic class templates are header-only. To use them, include the headers indirect.h and polymorphic.h in your project.

#include "indirect.h"

class Composite {
  xyz::indirect<A> a_; // a_ owns an object of type A
  xyz::indirect<B> b_; // b_ owns an object of type B
public:
  Composite(const A& a, const B& b) :
    a_(a),
    b_(b) {}

  // ...
};
#include "polymorphic.h"

class CompositeWithPolymorphicMembers {
  xyz::polymorphic<X> x_; // x_ owns an object of type X or derived from X
  xyz::polymorphic<Y> y_; // y_ owns an object of type Y or derived from Y
public:
  template <typename Tx, typename Ty>
  Composite(const Tx& x, const Ty& y) :
    a_(std::in_place_type<Tx>, x),
    b_(std::in_place_type<Ty>, y) {}

    // ...
};

Compiler explorer

You can try out indirect and polymorphic in Compiler explorer by adding the includes:

#include <https://raw.githubusercontent.com/jbcoe/value_types/main/indirect.h>
#include <https://raw.githubusercontent.com/jbcoe/value_types/main/polymorphic.h>

Compatibility

We have C++14 implementations of indirect and polymorphic available as indirect_cxx14.h and polymorphic_cxx14.h.

C++14 implementations can be tried out in compiler explorer by using the includes:

#include <https://raw.githubusercontent.com/jbcoe/value_types/main/indirect_cxx14.h>
#include <https://raw.githubusercontent.com/jbcoe/value_types/main/polymorphic_cxx14.h>

or by including headers indirect_cxx14.h and polymorphic_cxx14.h into your project.

We duplicate some code between the C++20 and C++14 implementations so that single-file includes work.

License

This code is licensed under the MIT License. See LICENSE for details.

Talks and presentations

We spoke about an earlier draft at C++ on Sea in 2022.

There are some significant design changes since this talk was given (after feedback and discussion at a C++ London meetup). We've pared down the number of constructors and made the null state unobservable.

Developer Guide

For building and working with the project, please see the developer guide.

GitHub codespaces

Press . or visit [https://github.dev/jbcoe/value_types] to open the project in an instant, cloud-based, development environment. We have defined a devcontainer that will automatically install the dependencies required to build and test the project.

References

value_types's People

Contributors

ben-craig avatar jbcoe avatar jwakely avatar lewissbaker avatar nbx8 avatar philipcraig avatar quuxplusone avatar sean-parent avatar stuartabercrombie avatar twon avatar ukilele avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

value_types's Issues

Fix odd compile errors in SBO implementation of Polymorphic when constructing classes using pmr allocators

To reproduce the error:

  1. Remove the preprocessor check in polymorphic_test.cc:
    #ifndef XYZ_POLYMORPHIC_USES_EXPERIMENTAL_SMALL_BUFFER_OPTIMIZATION

just before

TEST(PolymorphicTest, InteractionWithPMRAllocators)

  1. Build polymorphic_sbo_test

There are errors in finding a valid constructor - for some reason allocator traits are trying to pass the allocator to the constructed object. We don't see this with other allocators or with the regular (non-SBO) polymorphic implementation.

Add missing requires clauses

My minimal implementations don’t make any of the checks that they perhaps should. We’d get better compiler errors with the addition of concept checks. We need to be sure that incomplete types are still supported (like in indirect_value) though as that’s a useful use case (Pimpl).

Ideally we could check that the newly added requirements are doing the right thing in tests.

Ensure Pointer type is allocator::pointer

This is be clear in the draft and implemented in the ref implementation so this is useable with offset pointer. For instance, this is needed to use with boost::interprocess allocators

Potential issue with optional<indirect> specialization

#include <optional>
#include <cassert>

#include <https://raw.githubusercontent.com/jbcoe/value_types/main/indirect.h>

template<class T, class U>
T exchange(std::optional<T>& opt, U&& value)
{
    assert(opt);
    T ret{std::move(*opt)};
    *opt = std::forward<U>(value);  // avoid unnecessary branch
    return ret;
}

int main()
{
    // This code will work for any T in place of int...
    std::optional<int> oi{3};
    auto ri = exchange(oi, 2);

    // ... except indirect<T> if we have a specialization for optional<indirect> 
    std::optional<xyz::indirect<int>> oii{3};
    auto rii = exchange(oii, xyz::indirect<int>(2));
}

https://godbolt.org/z/xb3M3jcse

Add discussion of noexcept

At the discussion in the LWEG group in Kona, Hawaii 2023 it was suggested that this type should follow the Lako rule. However these value types have been build to model std::unique_ptr and allocator aware types such as std::vector

Variadic constructor of `indirect` is too greedy

In PR #128 we removed the parameter std::in_place_t from the variadic constructor of indirect:

template <class... Ts>
- explicit constexpr indirect(std::in_place_t, Ts&&... ts);
+ explicit constexpr indirect(Ts&&... ts);

The following snippet shows a simplified version of indirect and a (potentially contrived) scenario, where the variadic constructor might be too greedy:

#include <concepts>

template<class T>
struct indirect {
    indirect() = default;

    template<class ...Ts>
    explicit indirect(Ts&&... ts)
        requires std::constructible_from<T, Ts...> {}
};

struct Evil {
    Evil(indirect<Evil>&) {}
};

int main() {
    indirect<Evil> i1;
    indirect<Evil> i2(i1);
}

https://compiler-explorer.com/z/KWvzcnjY9

In the line indirect<Evil> i2(i1); we don't call the copy constructor of indirect (as I would expect as a user of indirect), but we call the variadic constructor as it is a better match. Note that it is only a better match as i1 is non-const. For a const indirect<Evil> the copy-constructor would still be called.
The first question is whether you all agree with me that indirect<Evil> i2(i1); should indeed call the copy-constructor or if you think that it is intentional design that it calls the variadic constructor. And if you agree, I think a fix would be to constraint the variadic constructor further. Something like:

template <class... Ts>
explicit constexpr indirect(Ts&&... ts)
- requires std::constructible_from<T, Ts&&...>;
+ requires (std::constructible_from<T, Ts...> &&
+     ( sizeof...(Ts) != 1 || !(std::same_as<std::remove_reference_t<Ts>, indirect> || ...) )
+ );

In prose: If the pack Ts consists of only one type U, make sure that U is not indirect&.

Forward all comparison operator

There was discussion in LWEG suggesting this should be based on regular types, however, this is not inline with the intended design rational which just passes through underlying type operation. Add a discussion point expanding on this.

Formatting of non-const-formattable types

Hi, this might be a contrived issue.
DRAFT.md#xy13-formatter-support-indirectfmt mentions the specialization of std::formatter<indirect<T, Alloc>, charT> when the underlying T supports specialisation of std::formatter<T, charT>. The format-function has the following signature (note that it takes the indirect by const&):

template<class FormatContext>
  typename FormatContext::iterator format(
    indirect<T, Alloc> const& value, FormatContext& ctx) const;

But there are some types T, where you can std::format a T&, but not a const T&. An example for such a type is std::ranges::filter_view<V, P>. In the following (contrived) example you can see how I can format a filter_view, but not an indirect<filter_view>:

#include <https://raw.githubusercontent.com/jbcoe/value_types/main/indirect.h>
#include <https://raw.githubusercontent.com/jbcoe/value_types/main/polymorphic.h>
#include <vector>
#include <ranges>
#include <print>

struct AllIn {
    bool operator()(int i) const { return true; }
};

int main() {
    std::vector v{1, 2, 3};
    auto filter = std::views::filter(v, AllIn{});
    std::println("filter = {}", filter);

    using FilterView = decltype(filter);
    xyz::indirect<FilterView> indirect(std::in_place, std::views::all(v), AllIn{});
    std::println("*indirect = {}", *indirect);
    std::println("indirect = {}", indirect); // error
}

So far I think this is a very contrived example and issue. But I at least wanted to make aware of it. In the future it might be worth it to extend formatting of indirect<T, A> to also work with non-const-formattable types.

Make `swap` ADL-proof

In the implementation of indirect and polymporphic swap gets called 8 times. Sometimes the member functions indirect::swap and polymorphic::swap (e.g. swap(tmp);) get called. And sometimes std::swap (https://eel.is/c++draft/utility.swap#lib:swap) on two pointers (e.g. swap(p_, other.p_) with using std::swap in the line above) gets called. The point is that swap gets called unqualified, i.e. it is subject to ADL. But we already know which overloads of swap we want to call and don't need/want different overloads to be picked by ADL. While there is no advantage in calling swap unqualified, there might be a disadvantage in doing so: I came up with an example, even though it is very contrived:

template<class T>
struct Holder { T value; };

class Incomplete;

template<class T>
struct X {};

using Indirect = xyz::indirect<X<Holder<Incomplete>>>;

void TestSwap(Indirect& a, Indirect& b)
{
    a.swap(b);
}

The snippet does not compile, as a.swap(b) makes an unqualified call to swap here. The arguments (p_ and other.p_) are of type X<Holder<Incomplete>>*. To perform ADL, the compiler has to instantiate several types, including Holder<Incomplete>. If you want to protect against this issue, I suggest to fully qualify all calls to swap.

If you want to have a more in depth discussion of the topic, I can recommend ADL can interfere even with uglified names and What is the std::swap two-step?.

Question about who no small object optimization?

This is just a question, sorry that I do not know where else to ask this.

I was skimming over the polymorphic_value papers and noticed that in the original paper small object allocation is encouraged, while in the new paper it is expressly called out as not supported. I would just like to better understand the reason for the change? Was this due to something learned from the implementation, or perhaps a reconsideration of the tradeoffs involved?

The new paper does not provide much explanation, other than saying that a small object implementation would need to be a different type (true, but does not explain why that type was not chosen for the implementation).

Publish draft paper

Requires feedback from #33 to be incorporated.

Go to isocpp.org to submit the paper with a new number

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.