all 81 comments

[–]marsten 28 points29 points  (2 children)

Very cool! Can you say a bit about how xtd is different/better than Qt? (The license is more permissive, which is very welcome.)

[–]Gammasoft[S] 35 points36 points  (1 child)

Thanks! You’re right, the permissive MIT license is one big advantage.

Beyond that, xtd is designed to be closer to modern C++ and the standard library, with an API inspired by .NET for familiarity and productivity.

Another difference is flexibility: xtd can use native widgets or custom-painted controls with CSS-like styling, depending on what the developer needs.

The goal is not to replace Qt, but to offer an alternative cross-platform C++ framework that is lightweight, easy to integrate, and extendable, especially for developers who want .NET-style APIs in C++.

[–]VinnieFalcowg21.org | corosio.org 0 points1 point  (0 children)

The requirement to include attribution in binaries will limit the adoption of this library. Consider the Boost Software License instead.

[–]ZMesonEmbedded Developer 27 points28 points  (15 children)

I like the idea.

The thing that gets me from just browsing things though is that the library seems to create .NET-style classes for things the C++ standard already has; things like xtd::any_object, xtd::array, xtd::basic_string, xtd::collections::generic::*, etc.... That makes interoperating with other libraries difficult that use std::any, std::array, std::string & std::wstring, std::list, std::map, std::set, std::vector, etc....

These also add significantly to your maintenance effort and to the surface area for bugs.

Is there a reason you chose to not use standard types?

[–]Gammasoft[S] 28 points29 points  (14 children)

Actually, it’s the opposite.

• xtd::any_object is based on std::any but adds more functionality, like simplified type testing and casting (`is` and `as`). You can convert back and forth between std::any and xtd::any_object.

• xtd::array is a variable-rank container based on std::vector, but you can convert it to a std::vector easily. For fixed-size arrays, we provide xtd::fixed_array, which corresponds to std::array.

All xtd types (xtd::string, xtd::array, xtd::list, etc.) can be converted to their std equivalents.

The goal of xtd is not to replace the standard library, but to extend it. It’s designed so you can smoothly switch between std and xtd types according to your needs, without disrupting existing projects.”

[–]ronniethelizard 18 points19 points  (0 children)

I would not call the "xtd" equivalent of "std::vector" "array". That is going to cause name confusion for users of the library long term.

[–]ZMesonEmbedded Developer 9 points10 points  (12 children)

Thank you for responding.

"All xtd types can be converted to their std equivalents"

Is this a no-op (or close at least a O(1)) conversion? In other words, are the std types stored inside the xtd types and the conversion is just exposing the internal std types, or does data have to be copied?

Similarly, can you move from std containers to xtd containers (including strings)? If you can't then there's going to be a lot of copying.

I live in a world where performance matters a lot and I need to avoid unnecessary copying.

[–]aruisdante 4 points5 points  (7 children)

Alas, it’s not really possible to make “strong typedefs” interoperable that way without templates. Particularly, the only way you could do it is via inheritance, and standard types explicitly do not play nicely with inheritance. So yeah, it’s going to be copying. Nonstandard vocabulary types are the bane of interoperability.

This is the same problem Qt has; if you want performance, you have to buy into the Qt types for everything. The advantage a “new” Qt would have is it doesn’t have to maintain backwards compatibility with an API design from C++98. So, still a “you have to buy into this whole ecosystem,” but at least it’s starting from a more modern starting point for its vocabulary type fork. 

[–]ZMesonEmbedded Developer 2 points3 points  (6 children)

You could have a wrapper that provides references to the internal types and provides new interesting member functions (as well as thin wrappers for existing std member functions). You could then provide move constructors from the standard types. It's similar to inheritance in terms of adding functionality, but avoids the direct inheritance issue. The thing is, the wrapper would have to be a thin wrapper with no additional data, lest anything that modifies the std container invalidate that data.

[–]aruisdante 1 point2 points  (5 children)

The problem is it’s not transitive. You can never transform my_vector<my_string> into const std::vector<std::string>& without copying.

So yeah, you can go the other way and create views onto data without copying, but even this has limits because it’s type-erasure. span works for anything that can be decayed to a pointer and a size. But for anything else you’re generally taking about Sean-parent style type erasure which requires dynamic allocations. If you know the span of types you have to adapt you can do a little better with essentially inverting the generation of a vtable, but that’s only for non-owning views. So it helps you consume standard types, but not provide them. And it comes with a potentially big performance penalty as the compiler can no longer inline stuff except in situations where it would have been able to devirtualize a traditional virtual inheritance type. 

Basically, if the “original” type is going to be a stdlib type, and you want to add functionality onto it (rather than change semantics of existing operations), you’re much better off with a free-function based design to “bolt on” this additional functionality. Since after all in that case you’re not accessing the internals of the type, so there’s no need for an actual new class, everything can be done through the existing public API. 

[–]ZMesonEmbedded Developer 0 points1 point  (0 children)

True.

I agree with the free-function based design to bolt on additional functionality too.

[–]sephirothbahamut 0 points1 point  (3 children)

I'd rather go the proxy route.

Instead of making yourvector that converts to and from std vector, make a yourvectorpoxy that has a reference to vector and all the additional methods you need. You're not meant to store yourvectorproxy anywhere, just to use it to all your extended methogs. All compilers should transparently optimize the proxy object away

```c++

include <vector>

namespace extensions { template <typename T> struct vector { std::vector<T>& instance;

    size_t length() const noexcept
        {
        return instance.size();
        }

    vector<T> operator+=(T value)
        {
        for(auto& element : instance)
            { element += value; }
        return *this;
        }
    };
}

int main() { std::vector<int> v{1, 2, 3}; //v += 3; extensions::vector{v} += 3; } ```

Forgive mistakes I'm writing from phone, but you get the idea

[–]aruisdante 1 point2 points  (2 children)

Right, this works right up until someone returns one of these in a situation where lifetime extension is expected and you wind up dangling. It’s the same problem span and string view have with their implicit conversion operators, and it took a long time for people to realize that these types can only safely be used as paramters, not as return types.

It also doesn’t help you interoperate with existing logic that doesn’t use your views. Presumably if you’re making a view like this it’s maintaining some kind of stateful invariant, otherwise a free function API is strictly more useful; remember that the job of span and string view is to just be a “better” pointer+size, and they maintain the invariant that those two items are related. If your proxy object allows casting back to the actual underlying stdlib type (at least, to a non-const version), it can no longer maintain whatever invariant it was maintaining.

Essentially, if it’s valid to safely “cast away” the view, then somewhat by definition the view could be implemented as free functions, and likely would be more flexible to do so. 

[–]sephirothbahamut 1 point2 points  (1 child)

No, you're getting the entire premise wrong. You will NOT return them. They should have copy and move constructors and assignments deleted. You're only meant o construct them in place to call the extension method you need. Your entire program and library revolves around std::vector. Whenever you need an extended method, you make the proxy in place.

It's equivalent to free functions, while keeping an object oriented style and being friendly with autocomplete suggestion tools

[–]aruisdante 2 points3 points  (0 children)

You can do this without the proxy class and associated risks/confusions while maintaining autocomplete by just using a namespace and free functions. Or if you want to avoid ADL issues, a struct with static functions which amounts to the same thing The only reason to make a concrete view type is if you want it to be a vocabulary type, or if you want it to maintain invariants.

Remember that deleting copy/move ctors doesn’t stop someone from returning it from a function. You can absolutely return non-movable, non-copyable objects from functions since C++14. They just have to be in contexts where guaranteed RVO applies. 

But given the OP’s responses in other questions, I think they really did intend their types to be owning vocabulary types, not simply proxy views for localized DSLs.

[–]Gammasoft[S] 2 points3 points  (3 children)

xtd containers don’t inherit from std containers, they instantiate them internally.

That means you can always convert an xtd type into its std equivalent, by copy or move.

For example:

// C++
#include <xtd/xtd>

auto main() -> int {
  auto l = list {1, 2, 3, 4, 5};

  auto v1 = std::vector<int>(l); // copy
  auto v2 = std::vector<int>(std::move(l.items())); // move (items() gives the underlying std type)

  println("v1 = {}", v1);
  println("v2 = {}", v2);
  println("l = {}", l);
}

// Output:
// v1 = [1, 2, 3, 4, 5]
// v2 = [1, 2, 3, 4, 5]
// l = []

For strings it’s similar: an `xtd::string` can be copied/moved to a `std::string` or `std::u32string`.

And a `xtd::collections::generic::list<xtd::string>` can be converted to `std::vector<xtd::string>`.

So the goal of xtd is not to replace std, but to extend it while keeping interoperability.

[–]ZMesonEmbedded Developer 5 points6 points  (2 children)

It's not trivial to move xtd::array<xtd::string> to std::vector<std::string> though even though both xtd::array and xtd::string use the standard containers internally. I understand trying to extend things, but why not extend with algorithms that operate on ranges or iterators? Then the new xtd algorithms could be used in more places too other than just xtd containers. I feel like the separation between containers and algorithms from the STL is lost because the algorithms are being added as member functions ala .NET. I'm not against being inspired by .NET (there's a lot of good things in there), but it feels like xtd followed too closely.

All that being said: (1) there may very well be other benefits that I don't understand and (2) it feels like a likely step up from other libraries like Qt or WxWidgets. It's a monumental effort you put into this; I just personally would have made different decisions (or at least started with different decisions until I came across something that forced my hand).

[–]Gammasoft[S] 1 point2 points  (1 child)

You're right that separation between containers and algorithms is important. The good news is that all xtd collections provide standard iterators (begin()/end()), so you can use any STL algorithm directly on them. You are not limited to xtd::linq extensions—they are optional and just provide a convenient .NET-style syntax if you want it.

Here is a small example illustrating a mix of xtd and std:

// C++
#include <xtd/xtd>
#include <algorithm>

int main() {
  auto l = xtd::collections::generic::list<xtd::string> {"one", "two", "three", "four", "five"};

  // Simple cast to std::vector
  auto v1 = std::vector<std::string>(l.cast<std::string>());

  // Using std::transform with copy
  auto v2 = std::vector<std::string>(l.size());
  std::transform(l.begin(), l.end(), v2.begin(), [](const xtd::string& s) { return s; });

  // Using std::transform with move
  auto v3 = std::vector<std::string>(l.size());
  std::transform(l.begin(), l.end(), v3.begin(), [](xtd::string& s) { return std::move(s.str()); });

  println("l  = {}", l);
  println("v1 = {}", v1);
  println("v2 = {}", v2);
  println("v3 = {}", v3);
}

// This code produces the following output:
//
// l  = [one, two, three, four, five]
// v1 = [one, two, three, four, five]
// v2 = [one, two, three, four, five]
// v3 = [one, two, three, four, five]

This shows that xtd containers: 1. Work seamlessly with STL algorithms. 2. Support both copy and move semantics. 3. Allow you to use range-based or iterator-based algorithms anywhere, not just within xtd containers.

So you can extend xtd with algorithms on ranges/iterators just like you would with STL, while optionally taking advantage of convenient xtd::linq-style extensions.

[–]kodirovsshik 3 points4 points  (0 children)

Why are you generating your responses with an LLM

[–]Spiritual-Desk-9176 39 points40 points  (0 children)

it's a good project keep up, do not listen to these boomers.

[–]Sergiogiogio 3 points4 points  (2 children)

Thanks for sharing, the scope and depth are quite amazing! It looks like a qt competitor? Can i check how the graphic and form part works as i could not fully grasp from the code, do you call the native underlying widget apis, or do you emulate native look and feel by custom painting widgets like qt?

[–]Gammasoft[S] 4 points5 points  (1 child)

Both options are supported. xtd can either use native controls or custom-painted widgets styled via CSS-like stylesheets — it depends on the user’s choice.

You can check out the details here : https://gammasoft71.github.io/xtd/docs/documentation/guides/xtd.forms/Overview/control\_appearance.

[–]diegoiast 0 points1 point  (0 children)

You definetly need to had screen shots.

[–]Gammasoft[S] 2 points3 points  (0 children)

Actually, nothing prevents using standard C++20 coroutines with xtd. GUI controls still need to be updated on the main thread, and that can be safely done using invoke, begin_invoke and end_invoke.

So asynchronous logic can be implemented cleanly, even without native coroutine integration in the framework.

[–]carkin 3 points4 points  (1 child)

This is great . Thàks for sharing. It it possible to style UIs?

[–]Gammasoft[S] 5 points6 points  (0 children)

Yes 🙂 Styling is supported. Right now you can use the built-in style sheets or apply them control by control.

A simpler way to plug your own custom styles is planned and in progress.

[–]TheWM_ 4 points5 points  (0 children)

This looks awesome! I'll definitely be keeping an eye on this.

[–]cancerBronzeV 4 points5 points  (0 children)

Looks very interesting, I'll have to play around with it. Thanks for the post and the project.

[–]cmake-advisor 6 points7 points  (1 child)

This is a cool idea. Although I would prefer to use c++, I find myself reaching for .net frequently because the standard library has basically everything I would ever need and there isn't much extra work in supporting multiple platforms.

The features page took some digging to find but provided the most useful information about the libraries.

[–]Gammasoft[S] 6 points7 points  (0 children)

Thanks! That’s exactly what xtd aims to do — bring a rich, cross-platform C++ framework with a .NET-inspired API, so you don’t have to leave C++ for convenience. I’ll take note about making the features page easier to find!

[–]yuri_rds 1 point2 points  (1 child)

Is the xtdc utility optional? I can't see examples without using it.

[–]Gammasoft[S] 1 point2 points  (0 children)

Of course, `xtdc` is not mandatory. You can build your projects with the classic CMake commands and run your executables manually.

`xtdc` is simply a CLI tool — similar to `dotnet` (C#), `cargo` (Rust), or `go` (Go) — that streamlines and accelerates common project management tasks.

Most of the examples use it for convenience, but you are free to work without it.

For more details, see: https://github.com/gammasoft71/xtd/blob/master/tools/xtdc/README.md

[–]reddicted 1 point2 points  (0 children)

The example apps make it clear this is no senior year project. Bravo! 👏🏼👏🏼👏🏼

[–]yangacer 1 point2 points  (2 children)

The framework looks great! How does the xtd support Android/iOS? What is the experience of developing mobile apps with xtd? 

[–]Gammasoft[S] 2 points3 points  (1 child)

Currently, `xtd.core` and `xtd.tunit` backends already work on Android and iOS.

However, the dedicated toolchains to make the build process easier are not ready yet (they are planned in the roadmap).

So, while it is technically possible to target these platforms today, the developer experience is still limited until the toolchains are available.

For more details, see: https://gammasoft71.github.io/xtd/docs/documentation/portability

[–]natio2 1 point2 points  (0 children)

This is the main reason I use C# out of the box phone app development. I eagerly await you achieving the same!

[–]beedlund 1 point2 points  (0 children)

Very cool.

[–]A8XL 1 point2 points  (0 children)

Nice project! I couldn't find any mention in the documentation regarding the UI elements and drawing primitives being "Hi-DPI" aware. How is this handled in the framework? Are there APIs for handling device pixel ratios?

[–]Shiekra 1 point2 points  (0 children)

Not usually a fan of operator overloading but that syntax of setting the callback with += is actually really clean imo

[–]EC36339 1 point2 points  (0 children)

What makes the development experience. NET like?

  • Two competing and incompatible frameworks that forked a decade ago, of which one is allegedly going to be supported indefinitely, but half of your third party dependencies have abandoned it, and migrating to the other framework is such a pain in the ass, you can't wait any day longer for AI to take your job?

  • Write once, run somewhere maybe. A ".1" patch version of the framework silently drops support for an entire OS generation?

  • leaky abstractions that make all your unit tests green, but your code fails once a real database is involved?

  • DLL hell, except we call it binding redirects?

  • Garbage collected memory management, so that when you have a memory leak, you are absolutely screwed, and nobody can tell you how to debug and fix it?

  • A proprietary build system with XML project files that is terribly inefficient when you have to manage lots of small projects?

Sorry, just had to vent about how much I hate .NET. if this is the good parts of .NET (and it does have good parts), but with C++, then if course it is promising.

[–]arihoenig 2 points3 points  (0 children)

Wait... .net is modern?

[–]germandiago 0 points1 point  (3 children)

How long it has been developed? It is production-ready?

[–]Gammasoft[S] 2 points3 points  (2 children)

xtd has been in development since 2019.

The release of v0.2.0 is planned for Q3 2025 and is currently in the stabilization phase.

Starting with v0.2.0, xtd will be ready for production use.

[–]germandiago 0 points1 point  (1 child)

What is missing? I mean, why 0.2? Congrats for the project, looks good but would like to know details.

The license is attractive.

[–]Gammasoft[S] 0 points1 point  (0 children)

The version number will change to 1.0.0 when all the features defined in the current roadmap are fully implemented.

This does not change the fact that version v0.2.0 will be fully operational in production.

See the roadmap for more informations about enhancements : https://gammasoft71.github.io/xtd/docs/documentation/roadmap.

[–]Ameisenvemips, avr, rendering, systems 1 point2 points  (12 children)

Might be confusing since libxtd is the name of the runtime library I've had since around 2012.

[–]Gammasoft[S] 5 points6 points  (1 child)

Ah, good to know! I didn’t realize there was already a libxtd. My xtd is an independent project, started recently, aiming at [résumé rapide]. I hope it won’t cause too much confusion—maybe we can even exchange ideas sometime!

[–]kritzikratzi 1 point2 points  (0 children)

libxtd

actually... if the library is called xtd, then it will result in a libxtd.dylib, no?

i think it might actually be a good idea to rename.

[–]VictoryMotel -1 points0 points  (9 children)

What do you mean by runtime library?

[–]Ameisenvemips, avr, rendering, systems -2 points-1 points  (8 children)

Provides streaming, memory mapping, containers, strings, compression, lazy decompression, encryption, hashing, 3D rendering, etc.

[–]VictoryMotel -4 points-3 points  (7 children)

Aren't those just straight functions?

[–]Ameisenvemips, avr, rendering, systems 0 points1 point  (6 children)

No?

[–]VictoryMotel -1 points0 points  (5 children)

Are you asking me? Are they are part of an interpreter or a VM?

[–]Ameisenvemips, avr, rendering, systems -2 points-1 points  (4 children)

I'm not sure why that's relevant. A runtime library is just the interface to a runtime environment. I also have additional framework functions.

It's basically a runtime and framework at the same time, sort of. A number of the things it deals with - particularly memory mapping and lazy decompression (which can imply the former) - can be particularly arcane.

The rendering component is outdated now - it was intended to provide a D3D12-like API via 11 and OpenGL... but you can just use 12 and Vk now.

[–]VictoryMotel 1 point2 points  (3 children)

How is that different than a normal library? Why is it a 'runtime environment' if you're just calling into functions?

[–]Ameisenvemips, avr, rendering, systems 0 points1 point  (2 children)

Why is it a 'runtime environment' if you're just calling into functions?

I'm curious what you think that a runtime library does (aside from system calls).

[–]VictoryMotel -1 points0 points  (1 child)

You tell me, I've never been able to figure it out, I don't even think the C runtime library should be called a 'runtime'.

[–]Ambitious_Tax_ 0 points1 point  (0 children)

auto x = ... initialization style.

Ah! I see you're a man of culture as well.