普通模板的结束和元模板从何处开始?

时间:2017-04-08 04:12:27

标签: c++ templates template-meta-programming

Jörg's answerthis问题很好地描述了“正常”模板(问题所指的,可能是错误的,作为泛型),它们操作在程序上运行的数据和元模板。 Jörg然后明智地提到程序数据所以它真的是一样的。也就是说,元模板仍然是一个不同的野兽。 普通模板的结束位置和元模板的开始位置是什么?

我能想到的最好的测试是,模板的参数是)import java.util.Scanner; public class ReplaceWords { public static String replaceWords(String s){ while(s.contains(""+"(") && s.contains(""+")")){ Scanner keyboard = new Scanner(System.in); String toBeReplaced = s.substring(s.indexOf("("), s.indexOf(")")+1); System.out.println("Enter the word with which you want to replace "+toBeReplaced+" : "); String replaceWith = keyboard.nextLine(); s = s.replace(toBeReplaced, replaceWith); } return s; } public static void main(String[] args) { String myString ="today is a (happy) day, I would like to (explore) more about Java."; myString = replaceWords(myString); System.out.println(myString); } } ,模板是“正常”,否则是元。这个测试是否正确?

4 个答案:

答案 0 :(得分:5)

边界:具有逻辑行为的签名

嗯,在我看来,边界线将被绘制,模板的签名停止成为一个简单的签名,产生运行时代码,并成为显式或隐式逻辑的定义,这将是在编译时执行/解决。

一些例子和解释

常规模板,即只有typename,class或可能的值类型模板参数,一旦在编译期间实例化,就会产生可执行的cpp代码。

代码(重要)未在编译时执行

E.g。 (非常简单,最可能不切实际的例子,但解释了这个概念):

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..."); 
}

编译完成后,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
}

此示例显示模板元编程(实际上是一个简单的示例......)。 这里发生的是,&#39; speak函数有一个模板化参数,在编译时解析并衰减到TNPC,如果为它传递的类型是从INPCWithVoice派生的

这反过来意味着,如果它没有,模板将没有实例化的候选者,并且编译已经失败。 查找SFINAE获取此技术:http://eli.thegreenplace.net/2014/sfinae-and-enable_if/

此时,在编译时执行了一些逻辑,整个程序在链接到可执行文件/库后将完全解析

另一个非常好的例子是:https://akrzemi1.wordpress.com/2012/03/19/meta-functions-in-c11/

在这里你可以看到阶乘函数的模板元编程实现,证明,如果元模板衰减为常量,即使字节码也可以完全等于固定值使用。

最终示例: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>();
}

此代码为位置N处的斐波纳契序列实现标量模板参数模板元编程。 另外,它显示了从10到0的循环计数的编译时间!

最后

我希望这会澄清一些事情。

请记住:loop和fibonacci示例为每个索引实例化上述模板!!!

因此,存在大量的冗余和二元膨胀!!!

我自己并不是专家,而且我确定在stackoverflow上有模板元编程功夫大师,他可以附加任何必要的信息。

答案 1 :(得分:1)

让我开始使用dictionary.com的定义来回答

定义

  

meta -

     
      
  1. 添加到主题名称的前缀,并指定另一个主题,分析原始主题,但更抽象,更高层次:元哲学;超语言学。

  2.   
  3. 一个前缀添加到有意识地引用或评论其自己的主题或特征的东西的名称上:一个元的绘画   艺术家画画布。

  4.   

模板编程是formost,用作在C ++类型系统中表达关系的一种方式。我认为可以公平地说,模板编程固有地利用了类型系统本身。

从这个角度来看,我们可以直接应用上面给出的定义。模板编程和元(模板)编程之间的区别在于模板参数的处理和预期的结果。

检查其参数的模板代码显然属于前一种定义,而从模板参数创建新类型可能会落入后者。请注意,这也必须与代码的意图相结合才能对类型进行操作。

实施例

我们来看看一些例子:

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;
};

此代码满足第二个条件,类型std::aligned_storage用于创建另一个类型。我们可以通过创建一个包装器来使这个更清晰

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

现在我们完成上述两个操作,我们检查参数类型T,提取其大小和对齐,然后我们使用该信息构造一个依赖于我们的参数的新类型。这显然构成了元编程。

原始的std::aligned_storage不太清楚,但仍然非常普遍。我们以类型的形式提供结果,并且两个参数都用于创建新类型。当创建type::data的内部数组类型时,可以发生检查。

论证完整性的反例:

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

在这里,您可能会遇到以下问题:

  

但优先级队列是否也进行类型检查,例如检索底层Container,或者评估其迭代器的类型?

是的,确实如此,但目标不同。类型std::priority_queue本身不构成元模板编程,因为它不利用信息在类型系统内操作。同时以下是元模板编程:

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

这里的目的是提供一种类型,而不是对数据本身的操作。当我们查看可以对每个代码进行的更改时,这会变得更加清晰。

我们可以更改std::priority_queue的实现,以更改允许的操作。例如,支持更快的访问,附加操作或容器内的位的紧凑存储。但所有这一切完全是为了实际的运行时功能而不关心类型系统。

相比之下,看看我们可以对PriotityQueue做些什么。如果我们要选择不同的底层实现,例如,如果我们发现我们更喜欢Boost.Heap或者我们反对Qt并且想要选择它们的实现,那就是单行更改。这就是元编程,我们在基于类型系统的参数中做出选择。

(Meta-)模板签名

关于您的测试,正如我们上面所见,storage_of只有typename参数,但非常清楚元编程。如果你挖得更便宜,你会发现类型系统本身是模板,Turing-complete。甚至不需要明确说明任何整数变量,我们可以通过递归堆叠模板(即自然数的Zermelo构造)轻松替换它们

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

在我看来,更好的测试是询问给定的实现是否具有运行时效果。如果模板结构或别名不包含任何仅在运行时发生效果的定义,则可能是模板元编程。

结束语

当然,正常的模板编程可能会使用元模板编程。您可以使用元模板编程来确定常规模板参数的属性。

例如,您可以选择不同的输出策略(假设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);
    }
}

这构成了使用元模板编程实现的功能的模板编程。

答案 2 :(得分:1)

尝试区分和定义术语

让我们首先尝试粗略定义这些术语。我首先对#34;编程&#34;进行了足够好的定义,然后重复应用&#34;通常&#34; meta-的含义:

编程

编程会导致程序转换某些数据。

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

我刚编写的代码会导致程序将某些数据(整数)转换为其他数据。

模板(元编程)

元编程导致&#34;程序&#34;将一些程序转换成另一个程序。使用C ++模板,没有有形的程序&#34;它是编译器的隐含部分。

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

我刚编写代码来指示编译器的行为类似于发出(代码)另一个程序的程序。

元模板(元元数据编程?)

编写元模板会产生一个&#34;&#34;程序&#34;&#34;这导致了一个&#34;程序&#34;这导致程序。因此,在C ++中,编写导致新模板的代码。 (来自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...>;
};

这是对编译器如何将两个给定模板转换为新模板的描述。

可能的异议

看看其他答案,有人可能会说我的元编程的例子不是真实的&#34;元编程,而不是&#34;泛型编程&#34;因为它没有在&#34; meta&#34;中实现任何逻辑。水平。但是,可以考虑给出编程的例子&#34; real&#34;编程吗?它也没有实现任何逻辑,它是从数据到数据的简单映射,就像元编程实例实现从代码(auto p = two_of_them(42);)到代码(模板&#34;填充&#)的简单映射一样34;具有正确的类型)。

因此,IMO,添加条件(例如通过专业化)只会使模板更复杂,但不会改变它的性质。

您的考试

明确。考虑:

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

foo是一个带有typename个参数的模板,但&#34;结果&#34;在一个模板中(名为foo::type ...只是为了保持一致性)&#34;结果&#34; - 无论给出什么参数 - 给予foo的类型(以及行为,由该类型实现的程序)。

答案 3 :(得分:0)

  

普通模板的结束和元模板在哪里开始?

当模板生成的代码依赖于编程的基本方面时,例如分支和循环,你已经越过了从普通模板到模板元编程的界限。

按照您链接的文章中的说明进行操作:

常规功能

bool greater(int a, int b)
{
   return (a > b);
}

只使用一种类型的常规函数​​(暂时忽略隐式转换)。

功能模板(通用编程)

template <typename T>
bool greater(T a, T b)
{
   return (a > b);
}

通过使用函数模板,您创建了可应用于多种类型的通用代码。但是,根据其用法,对于空终止的C字符串可能不正确。

模板元编程

// Generic implementation
template <typename T>
struct greater_helper
{
   bool operator(T a, T b) const
   {
     return (a > b);
   }
};

template <typename T>
bool greater(T a, T b)
{
   return greater_helper<T>().(a > b);
}

// Specialization for char const*
template <>
struct greater_helper<char const*>
{
   bool operator(char const* a, char const* b) const
   {
     return (strcmp(a, b) > 0);
   }
};

在这里,您已编写代码,如果说:

如果Tchar const*,请使用特殊功能 对于T的所有其他值,请使用泛型函数。

现在您已经超过了普通模板的阈值到模板元编程。您已经使用模板介绍了 if-else 分支的概念。