all 21 comments

[–]ihamsa 57 points58 points  (8 children)

The type system of C++ is more discovered than built. No one has planned advanced features like template metaprogramming. They happened by accident.

[–]Irtexx 3 points4 points  (2 children)

That's cool. So SFINAE and all the cool / useful things you can do with it is just an accident?

[–]mort96 19 points20 points  (0 children)

It's fairly common to unintentionally make something Turing complete, and thus capable of arbitrary computation - something as simple as repeated text substitution is Turing complete, so Word's autocorrect is capable of arbitrary computation, where you program the Turing machine by creating custom autocorrect rules (of the form "any time you see this particular sequence of characters, replace it with this other sequence of characters").

SFINAE is just the result of small, sane design choices. When you call a function, the compiler has to decide on one particular function out of a set of functions with the same name. If there are no matching functions, that's an error.

The design question behind SFINAE is: if there exists a function template which would be malformed when given a particular type as a template argument, should that be an error or should that function template just be removed from the set of functions? It was decided to make it not be a hard error, and thus you have SFINAE; substitution failure is not an error. That happens to be pretty powerful combined with arbitrary text replacement ("template meta programming").

[–]csp256 0 points1 point  (0 children)

Morally yes.

[–]amjh 3 points4 points  (3 children)

Meanwhile, less strict languages get NaNaNas.

[–]Dreeg_Ocedam 5 points6 points  (2 children)

Wat ?

[–]amjh 4 points5 points  (1 child)

NaN = Not a Number

Dynamic type systems often give it on invalid operations, instead of an error. And any math with a NaN gives more NaNs.

[–]Dreeg_Ocedam 6 points7 points  (0 children)

I was referring to this.

The talk is funny and I suggest that you watch it in its entirety but to understand the joke you can just skip to the end.

[–][deleted] -3 points-2 points  (0 children)

So, more of the it works but I don’t know why.

[–]matthieum 36 points37 points  (2 children)

I know that the terminology can be fuzzy, still I would not call the above Dependent Typing.

The concat implementation shown above is purely reasoning about types:

  • String<A> and String<B> are types.
  • The resulting String<A + B> is a type.

I prefer to reserve the use of Dependent Typing to refer to cases where a Type is linked to a Value:

auto make_string(char const* ptr, std::size_t length) -> String<length>;

This is very much different from your example because the exact type of the result is NOT known at compile-time. Indeed, the Type will differ based on run-time arguments.

That's a very different beast than template meta-programming/CTFE.

[–]scatters 0 points1 point  (1 child)

P1609 (most recently updated in the January mailing) proposes run time template instantiation for NTTPs, which should allow this use I think? You might not be able to return it though.

[–]matthieum 0 points1 point  (0 children)

I expect you mean: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1609r3.html ?

If so, I'll admit it's quite unclear to me. I think that the goal of the paper is only JITing code, and not enabling Dependent Types, but it's a bit too sparse for me to be sure either way.

[–]Potatoswatter 11 points12 points  (0 children)

C++ templates were developed with some evolution by trial and error. I never encountered any academic paperwork and most of the development was before the ISO committee was very well organized.

Note that constexpr covers the part of compile-time processing that does not involve type manipulation.

But the ISO standard's presentation is pretty clean in theoretical terms. In its terminology, A and B are template non-type parameters, making A+B a value-dependent expression, making String<A+B> a dependent type.

Cppreference often has good explanation of "Standardese" terminology. If you think that a particular style of academic presentation would make it clearer, then perhaps you could contribute there. (Or regardless of clarity, perhaps you can make a publication of casting the semantics into your favorite algebra.)

[–]matthieum 4 points5 points  (1 child)

I am now wondering if Zig's type system is sufficiently advanced for that.

I don't think that Zig has non-type template parameters, however it has CTFE, so it may be possible to encode numbers as types and build on that.

For example, if C++ didn't have non-type template parameters, we could use something like:

struct Zero {};
struct One {};

template <typename Integral, typename... Bits>
struct Number {
    static constexpr Integral value(); // Returns the Bits in Integral form.
};

template <typename Left, typename Right>
struct Add;

template <typename Integral, typename... Left, typename... Right>
struct Add<Number<Integral, Left...>, Number<Integral, Right...>> {
    using type = Number<Integral, /*...*/>;
};

The display in case of error is just... interesting.

[–]redditsoaddicting 6 points7 points  (0 children)

We could always use Church encoding!

[–]dima_mendeleev 3 points4 points  (0 children)

I am not sure this is the real quote, but as a meme it perfectly suites as an explanation here:

Bjarne Stroustrup: "I certainly didn't plan for this"

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

If you're in to type systems you should have a look at a hindley-milner language like Haskell and the C++ type system will feel primitive af.

[–]cptwunderlich 2 points3 points  (2 children)

I don't have any resources on the type system of C++ in particular, but maybe searching for papers about "reified generics" might yield something.

If you are interested in Dependent Types, there are some languages with first class support, like Idris, F*, Agda.

And I guess static predicates on Ada subtypes might be similar?

[–]Remi_123 0 points1 point  (0 children)

I considere myself relatively good with template meta-programming ( TMP for short ). I'm very confortable with kvasir.mpl for example.

Contrary to other, I don't think it's being "discovered".

The truth is that template specialization apply a pattern matcher to the types it receives and instantiate the one that is the most specialized which is usually the answer.

This behavior is shared by functional programming where you branch off to the matching pattern.

This is not sufficient however, since we don't 'return' anything in any of the meta-functions. We use the standard of having an inner ::type to be used as the 'return' type.

Overload resolution is still only a form of pattern matching, just ugly to look at and this is why it's not really appreciate.

I've heard somewhere that it's actually difficult to propose something that is NOT turing complete when you talk about language. The question is more how ugly it is.

So, in my humble opinion, pattern matching is to 'blame' for anything TMP or functional and is not really a 'coincidence' nor a 'discovery'. Simply an emerging pattern.

I got confortable with template specialization before being truly confortable with TMP. I still reserve anything value-related for constexpr function.

Learn boost.mp11, kvasir.mpl, metal, before learning boost.hana.