che*_*erd 7 c++ templates template-meta-programming
Jörg对这个问题的回答很好地描述了"正常"模板(问题所指的,可能是错误的,作为泛型),它们操作在程序上运行的数据和元模板.Jörg然后明智地提到程序是数据,所以它真的是一样的.也就是说,元模板仍然是一个不同的野兽.普通模板的结束和元模板从何处开始?
我能想到的最好的测试是模板的参数是独占的class还是typename模板是"正常的",否则是元.这个测试是否正确?
好吧,在我看来,边界线是绘制的,模板的签名停止为一个简单的签名,产生运行时代码,并成为显式或隐式逻辑的定义,将在编译时执行/解析.
常规模板,即仅具有typename,类或可能的值类型模板参数,一旦在编译期间实例化,就会生成可执行的cpp代码.
代码是(重要的)不在编译时执行
例如(非常简单且非常可能不切实际的例子,但解释了这个概念):
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...");
}
Run Code Online (Sandbox Code Playgroud)
编译完成后,root-template将用于实例化double类型的上述代码,但不会执行它. 此外,specialization-template将实例化为文本连接,但也包括:不在编译时执行.
但是这个例子:
#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
}
Run Code Online (Sandbox Code Playgroud)
此示例显示模板元编程(实际上是一个简单的示例...).这里发生的是,'speak'函数有一个模板化参数,如果为它传递的类型是从INPCWithVoice派生的,它将在编译时解析并衰减到TNPC.
这反过来意味着,如果没有,模板将没有实例化的候选者,并且编译已经失败.查找SFINAE获取此技术:http://eli.thegreenplace.net/2014/sfinae-and-enable_if/
此时,在编译时执行了一些逻辑,整个程序在链接到可执行文件/库后将完全解析
另一个很好的例子是:https://akrzemi1.wordpress.com/2012/03/19/meta-functions-in-c11/
在这里,您可以看到阶乘函数的模板元编程实现,证明即使字节码可以完全等于固定值使用,如果元模板衰减为常量.
#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>();
}
Run Code Online (Sandbox Code Playgroud)
此代码仅对位于N的fibonacci序列实现标量模板参数模板元编程.此外,它显示了从10到0的循环计数的编译时!
我希望这能澄清一些事情.
请记住:循环和斐波纳契示例为每个索引实例化上述模板!
因此,有一个可怕的冗余和二进制臃肿!
我自己不是专家,我确信在stackoverflow上有一个模板元编程功夫大师,他可以追加任何必要的信息.
让我们首先尝试粗略地定义这些术语。我希望从“编程”的一个足够好的定义开始,然后反复应用meta-它的“通常”含义:
编程的结果是转换一些数据的程序。
int add(int value) { return value + 42; }
Run Code Online (Sandbox Code Playgroud)
我刚刚编写了一个代码,该代码将生成一个将某些数据(整数)转换为其他数据的程序。
元编程产生将某个程序转换为另一个程序的“程序”。对于 C++ 模板,没有有形的“程序”,它是编译器行为的隐式部分。
template<typename T>
std::pair<T,T> two_of_them(T thing) {
return std::make_pair(thing, thing);
}
Run Code Online (Sandbox Code Playgroud)
我只是编写了代码来指示编译器的行为就像一个为另一个程序发出(代码)的程序。
编写元模板会产生一个“程序”,而“程序”又会产生一个程序。因此,在 C++ 中,编写会生成新模板的代码。(来自我的另一个回答:)
// 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...>;
};
Run Code Online (Sandbox Code Playgroud)
这是编译器如何将两个给定模板转换为新模板的描述。
看看其他答案,人们可能会说我的元编程示例不是“真正的”元编程,而是“通用编程”,因为它没有在“元”级别实现任何逻辑。但是,给出的编程示例可以被认为是“真正的”编程吗?它也不实现任何逻辑,它是从数据到数据的简单映射,就像元编程示例实现从代码(auto p = two_of_them(42);)到代码(模板“填充”了正确的类型)的简单映射一样。
因此,在我看来,添加条件(例如通过专门化)只会使模板变得更加复杂,但不会改变其本质。
绝对没有。考虑:
template<typename X>
struct foo {
template<typename Y>
using type = X;
};
Run Code Online (Sandbox Code Playgroud)
foo是一个具有单个typename参数的模板,但是模板中的“结果”(命名foo::type......只是为了一致性)“结果” - 无论给出什么参数 - 给定的类型foo(以及行为,程序)由该类型实现)。