Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where do normal templates end and meta templates begin?

Jörg's answer to this question nicely delineates between "normal" templates (what the question refers to, perhaps erroneously, as generics) which operate on data and meta templates which operate on a program. Jörg then wisely mentions that programs are data so its really all one and the same. That said, meta-templates are still a different beast. Where do normal templates end and meta templates begin?

The best test I can come up with is if a template's arguments are exclusively class or typename the template is "normal" and meta otherwise. Is this test correct?

like image 240
chessofnerd Avatar asked Apr 08 '17 04:04

chessofnerd


People also ask

What are the two types of templates?

There are two types of templates in C++, function templates and class templates.

What is template How many types of template are there in?

Technical overview. There are three kinds of templates: function templates, class templates and, since C++14, variable templates. Since C++11, templates may be either variadic or non-variadic; in earlier versions of C++ they are always non-variadic.

What are templates and its types?

A template is a C++ programming feature that permits function and class operations with generic types, which allows functionality with different data types without rewriting entire code blocks for each type.

What is the point of template metaprogramming?

Template metaprogramming (TMP) is a metaprogramming technique in which templates are used by a compiler to generate temporary source code, which is merged by the compiler with the rest of the source code and then compiled.


3 Answers

The boundary: Signature with Logical Behaviour

Well, in my opinion the boundary-line is to be drawn where a template's signature stops to be a simple signature yielding runtime-code and becomes a definition of explicit or implicit logic, which will be executed/resolved at compile-time.

Some examples and explanation

Regular Templates, i.e. with only typename, class or possibly value-type template parameters, produce executable cpp code, once instantiated during compile time.

The code is (important) not executed at compile time

E.g. (very simple and most likely unrealistic example, but explains the concept):

template<typename T>
T add(const T& lhs, const T& rhs) {
    return(lhs + rhs);
}

template<>
std::string add<std::string>(
                const std::string& lhs,
                const std::string& rhs) {
     return (lhs.append(rhs));
}

int main() {
    double      result = add(1.0, 2.0); // 3.0
    std::string s      = add("This is ", " the template specialization..."); 
}

Once compiled, the root-template will be used to instantiate the above code for the type double, but will not execute it. In addition, the specialization-template will be instantiated for the text-concatenation, but also: not executed at compile time.

This example, however:

#include <iostream>
#include <string>
#include <type_traits>

class INPCWithVoice {
    void doSpeak() { ; }
};

class DefaultNPCWithVoice 
    : public INPCWithVoice {
    public:
        inline std::string doSpeak() {
            return "I'm so default, it hurts... But at least I can speak...";
        }
}; 

class SpecialSnowflake
    : public INPCWithVoice {
    public:
        inline std::string doSpeak() {
            return "WEEEEEEEEEEEH~";   
        }
};

class DefaultNPCWithoutVoice {
    public:
         inline std::string doSpeak() {
            return "[...]";
        }
};

template <typename TNPC>
static inline void speak(
    typename std::enable_if<std::is_base_of<INPCWithVoice, TNPC>::value, TNPC>::type& npc) 
{
    std::cout << npc.doSpeak() << std::endl;
};

int main()
{
    DefaultNPCWithVoice    npc0 = DefaultNPCWithVoice();
    SpecialSnowflake       npc1 = SpecialSnowflake();
    DefaultNPCWithoutVoice npc2 = DefaultNPCWithoutVoice();

    speak<DefaultNPCWithVoice>(npc0);
    speak<SpecialSnowflake>(npc1);
    // speak<DefaultNPCWithoutVoice>(npc2); // Won't compile, since DefaultNPCWithoutVoice does not derive from INPCWithVoice
}

This sample shows template meta programming (and in fact a simple sample...). What happens here, is that the 'speak'-function has a templated parameter, which is resolved at compile time and decays to TNPC, if the type passed for it is derived from INPCWithVoice.

This in turn means, if it doesn't, the template will not have a candidate for instantiation and the compilation already fails. Look up SFINAE for this technique: http://eli.thegreenplace.net/2014/sfinae-and-enable_if/

At this point there's some logic executed at compile time and the entire program, will be fully resolved once linked to the executable/library

Another very good example is: https://akrzemi1.wordpress.com/2012/03/19/meta-functions-in-c11/

Here you can see a template meta programming implementation of the factorial-function, demonstrating, that even the bytecode can be entirely equal to a fixed-value use, if the meta-template decays to a constant.

Finalizing example: Fibonacci

#include <iostream>
#include <string>
#include <type_traits>

template <intmax_t N>
static unsigned int fibonacci() {
    return fibonacci<N - 1>() + fibonacci<N - 2>();     
}

template <>
unsigned int fibonacci<1>() {
    return 1;   
}

template <>
unsigned int fibonacci<2>() {
    return fibonacci<1>();    
}

template <intmax_t MAX>
    static void Loop() {
    std::cout << "Fibonacci at " << MAX << ": " << fibonacci<MAX>() << std::endl;
    Loop<MAX - 1>();
}

template <>
void Loop<0>() {
    std::cout << "End" << std::endl;    
}

int main()
{
    Loop<10>();
}

This code implements scalar template argument only template meta programming for the fibonacci-sequence at position N. In addition, it shows a compile-time for loop counting from 10 to 0!

Finally

I hope this clarifies things a bit.

Remember though: The loop and fibonacci examples instantiate the above templates for each index!!!

Consequently, there's a horrible amount of redundancy and binary bloat!!!

I'm not the expert myself and I'm sure there's a template meta programming kung fu master on stackoverflow, who can append any necessary information missing.

like image 112
MABVT Avatar answered Oct 27 '22 09:10

MABVT


Attempt to differentiate and define the terms

Let's first try to roughly define the terms. I start with a hopefully good enough definition of "programming", and then repeatedly apply the "usual" meaning of meta- to it:

programming

Programming results in a program that transforms some data.

int add(int value) { return value + 42; }

I just wrote code that will result in a program which transforms some data - an integer - to some other data.

templates (meta programming)

Meta programming results in a "program" that transforms some program into another. With C++ templates, there's no tangible "program", it's an implicit part of the compiler's doings.

template<typename T>
std::pair<T,T> two_of_them(T thing) {
  return std::make_pair(thing, thing);
}

I just wrote code to instruct the compiler to behave like a program that emits (code for) another program.

meta templates (meta meta programming?)

Writing a meta template results in a ""program"" that results in a "program" which results in a program. Thus, in C++, writing code that results in new templates. (From another answer of me:)

// map :: ([T] -> T) -> (T -> T) -> ([T] -> T)
//         "List"       "Mapping"   result "type" (also a "List")
// --------------------------------------------------------
template<template<typename...> class List,
         template<typename> class Mapping>
struct map {
  template<typename... Elements>
  using type = List<typename Mapping<Elements>::type...>;
};

That's a description of how the compiler can transform two given templates into a new template.

Possible objection

Looking at the other answers, one could argue that my example of meta programming is not "real" meta programming but rather "generic programming" because it does not implement any logic at the "meta" level. But then, can the example given for programming be considered "real" programming? It does not implement any logic either, it's a simple mapping from data to data, just as the meta programming example implements a simple mapping from code (auto p = two_of_them(42);) to code (the template "filled" with the correct type).

Thus, IMO, adding conditionals (via specialization for example) just makes a template more complex, but does not change it's nature.

Your test

Definitively no. Consider:

template<typename X>
struct foo {
  template<typename Y>
  using type = X;
};

foo is a template with a single typename parameter, but "results" in a template (named foo::type ... just for consistency) that "results" - no matter what parameter is given - to the type given to foo (and thus to the behavior, the program implemented by that type).

like image 33
Daniel Jour Avatar answered Oct 27 '22 11:10

Daniel Jour


Let me start answering using a definition from dictionary.com

Definition

meta -

  1. a prefix added to the name of a subject and designating another subject that analyzes the original one but at a more abstract, higher level: metaphilosophy; metalinguistics.

  2. a prefix added to the name of something that consciously references or comments upon its own subject or features: a meta-painting of an artist painting a canvas.

Template programming is formost used as a way to express relations in the type system of C++. I would argue it is therefore fair to say that template programming inherently makes use of the type system itself.

From this angle of perspective, we can rather directly apply the definition given above. The difference between template programming and meta (template-)programming lies the treatment of template arguments and the intended result.

Template code that inspects its arguments clearly falls into the former defintion while the creation of new types from template arguments arguably falls into the later. Note that this must also be combined with the intent of your code to operate on types.

Examples

Let's take a look at some examples:

Implementation of std::aligned_storage;

template<std::size_t Len, std::size_t Align /* default alignment not implemented */>
struct aligned_storage {
    typedef struct {
        alignas(Align) unsigned char data[Len];
    } type;
};

This code fulfills the second condition, the type std::aligned_storage is used to create another type. We could make this ever clearer by creating a wrapper

template<typename T>
using storage_of = std::aligned_storage<sizeof(T), alignof(T)>::type;

Now we fulfill both of the above, we inspect the argument type T, to extract its size and aligment, then we use that information to construct a new type dependent on our argument. This clearly constitutes meta-programming.

The original std::aligned_storage is less clear but still quite pervasive. We provide a result in the form of a type, and both of the arguments are used to create a new type. The inspection arguably happens when the internal array type of type::data is create.

A counter examples for completeness of the argument:

template<
    class T,
    class Container = std::vector<T>,
    class Compare = std::less<typename Container::value_type>
> class priority_queue { /*Implementation defined implementation*/ };

Here, you might have the question:

But doesn't priority queue also do type inspection, for example to retrieve the underlying Container, or to assess the type of its iterators?

And yes it does, but the goal is different. The type std::priority_queue itself does not constitute meta template programming, since it doesn't make use of the information to operate within the type system. Meanwhile the following would be meta template programming:

template<typename C>
using PriorityQueue = std::priority_queue<C>;

The intent here is to provide a type, not the operations on the data themselves. This gets clearer when we look at the changes we can make to each code.

We can change the implementation of std::priority_queue maybe to change the permitted operations. For example to support a faster access, additional operations or compact storage of the bits inside the container. But all of that is entirely for the actual runtime-functionality and not concerned with the type system.

In contrast look at what we can do to PriotityQueue. If we were to choose a different underlying implementation, for example if we found that we like Boost.Heap better or that we link against Qt anyways and want to choose their implementation, that's a single line change. This is what meta programming for, we make choices within the type system based arguments formed by other types.

(Meta-)Template signatures

Regarding your test, as we have seen above, storage_of has exclusively typename arguments but is very clearly meta programming. If you dig deaper, you will find that the type system itself is, with templates, Turing-complete. Without even needing to explicitely state any integral variables, we could for example easily replace them by recursively stacked templates (i.e. Zermelo construction of the natural numbers)

using Z = void;
template<typename> struct Zermelo;
template<typename N> using Successor = Zermelo<N>;

A better test in my eyes would be to ask if the given implementation has runtime effects. If a template struct or alias does not contain any definition with an effect only happening at runtime, it's probably template meta programming.

Closing words

Of course normal template programming might utilize meta template programming. You can use meta template programming to determine properties of normal template arguments.

For example you might choose different output strategies (assuming some meta-programming implementation of template<class Iterator> struct is_pointer_like;

template<class It> generateSomeData(It outputIterator) {
    if constexpr(is_pointer_like<outputIterator>::value) {
        generateFastIntoBuffer(static_cast<typename It::pointer> (std::addressof(*outputIterator));
    } else {
        generateOneByOne(outputIterator);
    }
}

This constitutes template programming employing the feature implemented with meta template programming.

like image 44
HeroicKatora Avatar answered Oct 27 '22 09:10

HeroicKatora