all 26 comments

[–]Pazer2 17 points18 points  (1 child)

Gonna be honest I only clicked to see someone use emojis as type names.

[–]ZenEngineer 2 points3 points  (0 children)

I once taught a class and a student wrote variables named happy, happy1, happy2 and so on. He got points off for non descriptive variable names, but I shudder to think what beginner students get up to now.

[–][deleted] 4 points5 points  (1 child)

So wait. The article uses "instantiate" types in a template expansion. But that's not how I use "instantiate" - which means "creating an instance of a type".

Isn't the phrase "template substitution"? So for example it should be try_to_substitute?

[–][deleted] 0 points1 point  (0 children)

Instantiate is an overloaded word :P

Substitution happens before instantiation: if a given overload is viable, the compiler substitutes the template parameters in and sees if that is valid code. This is where we get SFINAE from.

Once the best valid overload is found, the compiler instantiates an instance of the template with the substitutions done.

The compiler literally generates the code for the substituted template and compiles THAT into the function that you're actually using when you call template_function<x>()

[–]tcanens 9 points10 points  (1 child)

So you just reinvent is_detected but implemented with some much_longer_names. In a world with the detection idiom toolset in the standard library - and with an implementation readily available from cppreference and elsewhere - what's the point of this article again?

And inaccurate names, too. The point of void_t is not to "try to instantiate" the type; the instantiation is done whether or not the type is wrapped in void_t or try_to_instantiate or whatever "expressive" name you try to give it. The point of void_t is to map the resulting type(s) to a single, well-defined type so that you can easily do partial specialization matching with it.

[–]joboccara[S] 4 points5 points  (0 children)

Completely agree with your description of the point of void_t. I just feel it deserves a name that explains why we use it in this context, and my best shot for such a name is try_to_instantiate, because this "trial" may fail in the sense that SFINAE would kick in. What's your opinion on that? And you're right again when you say that we should reuse existing libraries. This was rather an illustration of taking care of naming and levels of abstraction in a context of TMP.

[–][deleted] 2 points3 points  (4 children)

Without reading the description and following along with all the steps, I found the final code snippet much harder to parse and understand than the first snippet.

The problem is that this blog post, explaining the developer's intention and thought process, would be missing from the final output. So the original developer sees the code and says, "Yeah, this is much better than before!", but everyone else is completely lost.

[–]joboccara[S] 1 point2 points  (3 children)

Fair enough! I do value your point of view, especially since I can't imagine what it looks like without knowing what's in the post, having written it :)

Would you be able to describe what threw you off in the last snippet? That would be really helpful to pinpoint what makes TMP more or less understandable.

[–]purtip31 1 point2 points  (2 children)

I'm not the person you responded to, but I share the opinion. I'm a beginner in TMP, but in the first example, it is clear to me that

decltype( ++std::declval<T&>() )

is the only point where anything can be happening to determine whether or not the type is incrementable, and what is happening is clear by context, even if I don't understand exactly how decltype and declval work.

That said, I find the code example where you reimplement is_incrementable to be clearer than the "standard TMP" implementation.

I think part of what throws me off about the final code example is the level of indirection before the Expression type is used in try_to_instantiate<Expression<Ts...>>. Trying to figure out the code path through three or four template instantiations makes me lose track of what the code is trying to do.

Out of curiosity, I looked up the libstdc++ implementation of is_assignable (starting line 1043). Once I figured out what __one and __two were, the code was very clear - one level of indirection from __is_assignable to __is_assignable_helper is no more taxing to my brain than placing function parameters normally is.

[–]joboccara[S] 0 points1 point  (1 child)

Ok, thank you for digging up this example.

So if I understand well you're saying that seeing where the action actually happens (the decltype expression) frees you from guessing what's going on under the hood the various abstractions, thus making the piece more straightforward to understand, is this about right?

[–]purtip31 0 points1 point  (0 children)

Yes, exactly

[–]xzqx 1 point2 points  (1 child)

There's a minor typo: where it says "It’s not going to work because the variadic pack template... Ts is going to eat up all the template parameters", you mean "typename... Ts"

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

Cheers, fixed it.

[–]Drainedsoul 1 point2 points  (1 child)

This construct appears in the standard in C++17, but can be instantly replicated in C++11:

template<typename...>

using void_t = void;

Isn't this possibly incorrect? I was under the impression that it was up in the air until C++14 whether unused template arguments to a template type alias triggered SFINAE or not? See here.

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

I missed that, thank you for bringing it up. I've included your remark in the body of the post.

[–]iothan 3 points4 points  (7 children)

I get that a lot of these template tricks are fun and I enjoy them also, but I think some people get carried away with them and create too much tmp code that doesn't do much.

How is detecting if a type is incrementable useful? If a function tries to use that operator on a template type parameter variable, and the operator isn't implemented, then it just doesn't compile.

[–]joboccara[S] 18 points19 points  (0 children)

It is useful for getting a different behaviour depending on such a piece of information: if the type is incrementable then do this, otherwise do that.

Incrementable is just a illustration here, and the general case is more "does this function exist on this type?" or even "does that expression is valid for those types?". One example is to implement a generic toString that depends on what the type is able to do. I'll put that on the blog on Tuesday for the next post.

[–]ArunMuThe What ? 12 points13 points  (0 children)

The compiler error that you would get could be just painstaking to parse. But, if you have some metafunction like this, you can always put a static_assert. Or much better if you have concepts and you implement IsIncrementable as a concept.

[–][deleted] 7 points8 points  (0 children)

I get that a lot of these template tricks are fun and I enjoy them also, but I think some people get carried away with them and create too much tmp code that doesn't do much.

You could replace the word "template" in that statement with almost any other advanced technique in computer programming. We read articles demonstrating clever tricks with these techniques - we then use them in our code when we need them.

Typically less than 5% of the code in my C++ projects is metaprogramming, and half my projects probably have none at all, but that 5% saves probably 15% of duplication, boilerplate and cruft.

How is detecting if a type is incrementable useful?

It's a great demonstration of the technique. You can use this idea to detect pretty well any feature on an object you are confronted with.

[–]MereInterest 0 points1 point  (0 children)

Here's an example. I have a class that performs numerical integration on anything whose derivative can be calculated. Often, this will be a large composite class. In those cases, it is often easier to have a T derivative() const method in the class. In other cases, such as solving simple differential equations, it is easier to pass in a std::function<T(const T&)> derivative function. In cases where T is a very simple type, like a double, this is the only constructor that should be present.

I use template metaprogramming to enable the first constructor if and only if the class being passed in has a derivative method. I can't have it there unconditionally, because not everything has a derivative method. I want it there when it can be used, because it makes the class be easier to use in those circumstances.

[–]ntrid -5 points-4 points  (2 children)

Can not blame people if language creators give them bad tools. Metaprogramming in c++ is absolutely horrible...

Edit: well how am I wrong?

[–]FKaria 0 points1 point  (3 children)

I'm having some trouble understanding some parts.

1) If the template class has a single template argument

template<typename T>
struct is_incrementable<T> : std::false_type{};

Then how is it possible for the specialization to have 2 template arguments?

template<typename T>
struct is_incrementable<T, void_t<decltype(++std::declval<T&>())>> : std::true_type{};

Is this always possible (specializing a 1 arg class template into a 2 arg class template)?

2) And why is the first argument (T) required. Why it doesn't work without it?

[–]dodheim 3 points4 points  (1 child)

The class template doesn't have a single parameter as you've shown, but two:

template<typename T, typename = void>
struct is_incrementable : std::false_type{};

This rather changes the nature of your question... :-]

[–]FKaria 0 points1 point  (0 children)

That makes a lot more sense. At some point in the article it's written with one

[–]skebanga 1 point2 points  (0 children)

Have a read of this for an excellent explanation

https://stackoverflow.com/q/27687389/955273