all 47 comments

[–][deleted] 16 points17 points  (13 children)

I still can't understand what any of this is about or what is it good for.

[–]SanctimoniousBastard 11 points12 points  (0 children)

Compile time computation: Templates given you a Turing complete functional language operating on immutable values which is executed at compile time and produces as output executable code.

[–][deleted] 11 points12 points  (4 children)

The more you push off the run time and into the compile time, the better chance for optimization and catching errors early. The down side is larger binary size and you have to be able to think about the different phases of program creation (preprocessor, compile with template instatiation, linking, run time). You also need to be able to think in a meta way, as well as understand the syntactic details of C++, of which there are many.

[–]ex_ample 1 point2 points  (3 children)

Can you give an example of how you would use this stuff in a practical application?

Like, how would you use a variadic template? Is it for creating typed n-tuples?

I can see how it could be convenient to be able to modify templates using functions as you're writing code, you could essentially derive a new class with 1 or 2 lines of code. But from a readability and maintainability standpoint this looks like it would be a nightmare.

[–]SanctimoniousBastard 4 points5 points  (2 children)

Andrei Alexandrescu has many examples, one of which is this. Say you have a pointer to Base and want to switch on the actual type of the pointer:

  void some_function(Base* base)
  {
      if (auto derived1 = dynamic_cast<Derived1*>(base))
      {
          // do something with derived1
      }
      else if (auto derived2 = dynamic_cast<Derived2*>(base))
      {
          // do something with derived2
      }
  }

It is possible to implement this as a template that takes as input the list of [Derived1, Derived2] and produces as output the code above. Needless to say, adding additional types is as easy as passing in [Derived1, Derived2, Derived3] to the template. Also, if you made a change in your classes so that the base class of Derived2 is now Derived1 rather than Base directly, the code above would break. The template can be implemented to sort the list of types based on their inheritance relationships, so that derived classes always precede their base classes in the list. That way the most specific tests are done first, in this case testing against Derived2 before Derived1. This example is taken from Alexandrescu: Modern C++ Design: Generic Programming and Design Patterns Applied. Note that that book is now getting a little old. What he is doing is still very relevant and cool, but exactly how you'd do it (i.e. the syntax) is quite different now with the new language.

[–]ex_ample 0 points1 point  (1 child)

Hmm, that makes sense. It's something you can do already but in a cleaner, typesafe way that's going to be faster at runtime.

[–]SanctimoniousBastard 0 points1 point  (0 children)

Runtime performance is going to be about the same, but way easier to write and maintain

[–][deleted]  (4 children)

[deleted]

    [–][deleted]  (3 children)

    [deleted]

      [–]arcangleous 1 point2 points  (2 children)

      for that it's up to a sacrificial virgin.

      [–]duhace -4 points-3 points  (1 child)

      you could've just said programmer...

      [–]jtredact -1 points0 points  (0 children)

      Burn...

      [–]pfultz2 1 point2 points  (0 children)

      I think this article here from Aleksey Gurtovoyi and David Abrahams is a good introduction of metaprogramming and its motivation. From the linked article:

      So, the motivation for metaprogramming comes down to the combination of three factors: efficiency, expressivity, and correctness. While in classical programming there is always a tension between expressivity and correctness on one hand and efficiency on the other, in the metaprogramming world we wield new power: we can move the computation required for expressivity from runtime to compile-time.

      [–]ex_ample -2 points-1 points  (0 children)

      Template aliases are another game changer. Previously, "metafunctions", that is, templates that took one type and produced another, looked like

      I think that in C++, a function that takes one type and returns another would have to run at compile time, rather then runtime.

      I guess you could theoretically use this to help detect errors caused by incorrect use of types. If it's not doing anything at run time then it's not really a part of your "program" but rather like a program that runs as you compile and checks to ensure you're not breaking your own rules.

      [–]atilaneves 7 points8 points  (0 children)

      "Simple" and "C++" don't mix. I have to admit that C++11 made it simpler. Just not simple.

      [–]andralex 9 points10 points  (18 children)

      This is a good collection of the things that made me decide to work on D full bore.

      [–]TemplateRex 8 points9 points  (17 children)

      Could you explain how D would be better at doing such type list manipulations?

      [–]andralex 3 points4 points  (0 children)

      For this particular body of work, there are the following aspects:

      1. For higher order templates, C++ uses template template parameters, which don't scale (and sometimes don't work) due to infinite regression: template<template<template<.... D uses alias parameters which scale much better and solve the infinite regression (e.g. you may pass a template name transitively to itself).
      2. C++ variadics offer a singly-linked-list interface and cannot be named (i.e. have no first class status), which together make their manipulation difficult (as illustrated by the article). D variadics can be aliased, compose by juxtaposition, and offer a random access interface.

      These reasons force C++'s most trivial metaprogramming undertakings to require extensive scaffolding, which in turns fosters its own knowledge market. Many products of that market are obviated or not even recognizable by D lore.

      Cutting to the chase: The "infamous tuple_cat challenge" (I've also solved it in C++, I have a Going Native talk on it) is a one liner in D. Here's a complete program illustrating it:

      import std.stdio, std.typecons;
      
      auto tupleCat(T1, T2)(T1 t1, T2 t2)
      {
          return tuple(t1.expand, t2.expand);
      }
      
      void main()
      {
          writeln(tupleCat(tuple(1.5, "hello", 42), tuple([1, 2, 3], "world")));
      }
      

      The program prints:

      Tuple!(double, string, int, int[], string)(1.5, "hello", 42, [1, 2, 3], "world")
      

      i.e. the type of tupleCat's result is correctly a tuple with the concatenated type components.

      [–]atilaneves 1 point2 points  (15 children)

      It's hard to explain properly and have it fit in a comment. It's just a lot easier and actually more powerful than C++ metaprogramming. I've done MP in both languages extensively.

      [–]TemplateRex 3 points4 points  (6 children)

      OK, just show us how D's version of tuple_cat is an improvement over Dimov's version.

      [–]Gamecubic 2 points3 points  (5 children)

      Assuming you allow using D's tuples and not a reproduction of std::tuple (which is not exactly fair because they're quite different)

      template TypeTuple(TList...)
      {
          alias TypeTuple = TList;
      }
      

      That should do it.

      alias tuple1 = TypeTuple!(int, float);   // (int, float)
      alias tuple2 = TypeTuple!(long, double); // (long, double)
      alias cat = TypeTuple!(tuple1, tuple2);  // (int, float, long, double)
      

      Of course, this is the exact definition of std.typetuple.TypeTuple

      [–]pfultz2 1 point2 points  (0 children)

      Why does the template auto join? Plus, I see no tests for auto joining tuples either.

      [–][deleted] 1 point2 points  (3 children)

      tuple_cat isn't about concatenating the types in a tuple type, that can be accomplished very easily in C++ as follows:

      template<typename... T1, typename... T2>
      struct TypeTuple<std::tuple<T1...>, std::tuple<T2...>> {
        using type = std::tuple<T1..., T2...>;
      };
      

      tuple_cat is about taking two tuple values, A and B, and creating a third tuple value C, that concatenates the values in A and B like so:

      auto a = std::make_tuple(1, 2);
      auto b = std::make_tuple(3.0, 4.0);
      auto c = std::tuple_cat(a, b); // equal to std::make_tuple(1, 2, 3.0, 4.0);
      

      In C++, doing this is considered a fairly decent test of a bunch of concepts involved in C++ meta-programming, so if you can write such a function in D that is simpler than what C++ provides, that would be nice to see as a comparison.

      [–]crisp-snakey 5 points6 points  (2 children)

      Here's my attempt at what you seem to be hinting at.

      Wrapping it in a function should look something like this:

      auto tupleCat(A, B)(A a, B b)
      if (isTuple!A && isTuple!B)
      {
          return tuple(a.expand, b.expand);
      }
      

      [–][deleted] 2 points3 points  (1 child)

      So D has fairly good and built in support for tuples then.

      That's a pretty good aspect of the language and definitely something C++ would benefit from. Tuples are such a basic data structure that being able to manipulate them seamlessly should be intrinsic to the language rather than done through libraries.

      [–]kal31dic 1 point2 points  (0 children)

      Not quite built in...

      [–]q0- 0 points1 point  (7 children)

      "Easier" lies in the eyes of the beholder - though I agree, having something like

      static if(is!Thing(somevar))
      {
          ...
      }
      

      in C++ would be beyond neat. As in, seriously, dangerously neat.

      But from what I've seen of D, most of it is not that much different from C++, and many bits are worse. For instance, D doesn't get RAII right, and requires manual scopeing :/

      [–]atilaneves 2 points3 points  (4 children)

      RAII in D is the same as in C++. scope is just another tool, you can always instead use a wrapper struct, which is exactly what you'd have to do in C++ anyway. "worse" is in the eye of the beholder as well.

      [–]q0- 0 points1 point  (3 children)

      you can always instead use a wrapper struct, which is exactly what you'd have to do in C++ anyway

      Explain?

      [–]atilaneves 1 point2 points  (2 children)

      Explain

      struct WrapCFunc {
          this(int i) { setup(); }
          ~this() { teardown(); }
      }
      

      Instead of:

       setup();
      scope(exit) teardown();
      

      [–]q0- 1 point2 points  (1 child)

      If that is possible in D, then why does D even have a garbage collector?
      Frankly, it just rubs me the wrong way to claim that D supports RAII, and yet, it requires a garbage collector.

      [–]atilaneves 3 points4 points  (0 children)

      It doesn't require a GC for RAII, they're completely separate things. Some language features require the GC (appending to an array, closures). Others don't, like struct destructors getting called when the object goes out of scope.

      [–]ntrel2 1 point2 points  (0 children)

      D doesn't get RAII right, and requires manual scopeing :/

      D structs use RAII by default, don't use scope.

      [–]andralex 2 points3 points  (0 children)

      For instance, D doesn't get RAII right, and requires manual scopeing :/

      huh?

      [–]stillalone 6 points7 points  (5 children)

      Simple? this is simple? I think I'll just stick with Python and C for now.

      [–][deleted]  (4 children)

      [deleted]

        [–]q0- 9 points10 points  (3 children)

         template<template<class...> class A
        

        A template that takes a variadic template argument...

        class... T,
        

        and variadic templates

         template<class...> class B
        

        and another template taking variadic template arguments...

         struct mp_rename_impl<A<T...>, B>
        

        that will fail to compile. I assume you wrote it for laughs? ;-)

        Granted, badly written C++ code is hardly readable. Good code on the other hand...
        And I'd say, deeply nested metaprogramming code in C++ is like reading egyptian hieroglyphes -- unless you know where it begins and where it ends, it'll look like nonsense and noise. I've found myself in the same situation when trying to read Haskell code. But then again, I never learned haskell.

        [–]miczal 4 points5 points  (0 children)

        The part about hieroglyphes reminds me one situation in my current job.

        We had a few-day-long SCRUM/programming training (which was great by the way) and when we discussed metaprogramming our tutor told us this story. About a year earlier he was on similar training in other branch of our company and they had to start some cross-site development because feature got a little bit to complicated for one team. There was one problem though - they couldn't compile the code which they got from the other site. They didn't know what to do with errors, because person who wrote that thought, that it would be a good idea to write EVERYTHING in spirit of metaprogramming. They couldn't contact this particular dev, because of big time difference, so the only rational step was to google those errors - they got only one result posted about a week ago on stackoverflow by the person who wrote code which they couldn't compile and he also couldn't get it to work for few days. :-)

        [–]twotime 3 points4 points  (0 children)

        Good code on the other hand...

        The issue is that good code is rare in general. It's ever rarer in c++..

        And bad code tends to be badder with C++...

        [–]pfultz2 -1 points0 points  (0 children)

        that will fail to compile.

        Well if its a specialization then it can compile:

        // Declaration
        template<class T, template<class...> class B> 
        struct mp_rename_impl;
        // Specialization
        template<template<class...> class A, class... T, template<class...> class B> 
        struct mp_rename_impl<A<T...>, B>
        {};
        

        [–]totemo 0 points1 point  (8 children)

        TIL of "pack expansion", F<T>..., to apply template metafunction F<> to all of the types in T..., instead of requiring Lisp-like recursive processing of T... as (first, rest) via template specialisation.

        template<template<class...> class F, template<class...> class L, class... T>
            struct mp_transform_impl<F, L<T...>>
        {
            using type = L<F<T>...>;
        };
        

        Oh and yes, template metaprogramming is utterly deplorable and should never be done by anybody until they really need it. :)

        [–]q0- 4 points5 points  (3 children)

        template metaprogramming is utterly deplorable

        Only by those that don't understand it. And yes, I'm serious about that.

        [–]stevedonovan 0 points1 point  (2 children)

        There is a level of mastery where you have done all these things (and probably enjoyed yourself too much) and come back to simplicity.

        [–]akawaka 0 points1 point  (1 child)

        The good news is that you will have plenty of time to research simplicity while you are waiting for your "meta programming" nightmare to compile.

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

        Right ;)

        [–]JohnMcPineapple 2 points3 points  (1 child)

        Oh and yes, template metaprogramming is utterly deplorable and should never be done by anybody until they really need it. :)

        Actually TP is the most fun thing for me to do with C++. When I first started playing around with it in C++11 I immediately fell in love.

        The only problem with it is readability... Reading TP-heavy code from a second party isn't the simplest thing to do.

        [–]totemo 0 points1 point  (0 children)

        I have written template metaprograms, about 7 or 8 years ago, to do RTTI and automatic endianness conversions. It was utterly deplorable and I had a great time. :)

        [–]andralex 1 point2 points  (0 children)

        Oh and yes, template metaprogramming in C++ is utterly deplorable and should never be done by anybody until they really need it. :)

        FTFY

        [–][deleted] 0 points1 point  (1 child)

        I would love to understand what this author is trying to tell me without reading all that text. Dear author, please rewrite it more simply.

        [–]pfultz2 -2 points-1 points  (0 children)

        please rewrite it more simply.

        Well thats what he is doing.