Hacker News new | ask | show | jobs
by byuu 4304 days ago
I've been quite disillusioned with C++ myself as of late. I am currently writing a wrapper class around shared pointers to objects with inheritance (which embed their own private implementations, which also utilize inheritance) so that I can pass them by value and use operator. for method chaining. The SFINAE used for construction from base objects to these shared pointer wrappers is something out of the ninth circle of hell.

    template<typename T> struct Shared : std::shared_ptr<T> {
      template<typename U> struct is_compatible {
        typedef char yes[1], no[0];

        template<typename V> static yes& test1(typename std::enable_if<std::is_base_of<std::shared_ptr<T>, V>::value>::type*);
        template<typename V> static no& test1(...);

        template<typename V> static yes& test2(typename std::enable_if<std::is_base_of<element_type, typename V::element_type>::value>::type*);
        template<typename V> static no& test2(...);

        static constexpr bool value = sizeof(test1<U>(0)) == sizeof(yes) || sizeof(test2<U>(0)) == sizeof(yes);
      };
And from the function binder:

      //value = true if R L::operator()(P...) exists
      template<typename L> struct compatible {
        template<typename T> static constexpr typename std::is_same<R, decltype(std::declval<T>().operator()(std::declval<P>()...))>::type exists(T*);
        template<typename T> static constexpr std::false_type exists(...);
        static constexpr bool value = decltype(exists<L>(0))::value;
      };
      template<typename L> function(const L& object, typename std::enable_if<compatible<L>::value>::type* = nullptr) { callback = new lambda<L>(object); }
And yet ... when actually using the library, it is a thing of beauty.

    struct TextEditor : Window {
      MenuBar menuBar = {this};
        Menu menuFile = {&menuBar, "File"};
          MenuItem menuQuit = {&menuFile, "Quit"};
    
      VerticalLayout layout = {this};
        TextEdit editor = {&layout, Size{~0, ~0}};
    
      TextEditor() {
        StatusBar statusBar{this};
        statusBar.setFont(Font::sans(8, "Bold")).setText("Line 1, Column 1");

        HorizontalLayout findBar(&layout);
        findBar.append(Label(&findBar, "Find:"));
        findBar.append(LineEdit(&findBar).setBackgroundColor(Color::Yellow)
        .onChange([&] { searchFor(text()); }));
        findBar.append(Button(&findBar, "Clear")
        .onActivate([&] { findBar.widget(1).setText(""); }));
    
        menuQuit.onActivate(&Application::quit);
        edit.onChange([&] { updateStatusBar(); });
      }
    };
There is no need for any memory management, or any usage of pointers. We build UIs, and everything gets automatically released safely when nothing is referring to it anymore. We can declare named objects that we can use later, or we can create dynamic objects and pop them right inside of other objects. We can destroy and unparent things whenever we want. And it's deterministic, reference-counted GC. No pauses for a tracer. No dynamic typing anywhere, all errors are at compile-time.

I highly suspect that C++ is unreasonably complicated and that all of this rvalue-reference, variadic template, meta-programming, dynamic-casting polymorphism, is all just voodoo that isn't applicable to general programming. And yet, being able to do it gives me amazing expressive power to write awesome libraries that I could never hope to accomplish in another language.

Until I find a language that's even in the same ballpark as C++ in terms of performance, and offers similar expressiveness, it really doesn't even matter how bad C++ can be for library authors. There's no other viable option right now. D is the closest we have, but its complexity already rivals, if not exceeds, that of C++.

1 comments

A side note: I think you may be able to simplify `is_compatible` by getting rid of hand-rolled `yes` and `no` constants (and subsequent manual size-testing for `value` computation) and using `std::true_type` and `std::false_type`, respectively: http://en.cppreference.com/w/cpp/types/integral_constant

Edit: noticed you're using these in another place (`compatible` implementation), so perhaps there's a reason for a different approach?

Thanks, the is_compatible test certainly gave me a good bit of trouble.

Ideally you'd want to do enable_if< conditionA || conditionB >, but of course if one of the conditions fails to evaluate, the overload is ignored. So you have to split out the conditions and them merge them back together later on.

We could use true_type / false_type, but they have equivalent sizes. So unlike the function version that only needs one test and can just take the return type directly, the test at the end would then have to become std::is_same<decltype(test1<U>(0)), std::true_type>::value | std::is_same<decltype(test2<U>(0)), std::true_type>::value.

I still think we can do better than even this, so I'll have to keep working at it.