C++模板元编程 --- 第二章 习题答案

这一章的主题是类型识别,所以习题也都是围绕类型识别来设置的。

2-0

编写一个一元元函数 add_const_ref, 如果T是一个引用类型,就返回T,否则返回T const&。(可以使用std::is_same验证你的函数结果)
第一道题目,我承认我还没get到元编程的精髓是编译期的处理(注意:和宏不一样,宏是预编译期的代码替换),精髓在于编译期的类型替换,而我,差点就写出这种:

template<typename T>
T& add_const_ref()
{
    //如果是引用,就返回T
    //如果不是,就返回T const &
    return (std::is_reference<T>::value)?T:T const & ;
}

啧啧啧,这可是运行期的特征啊。
题目中说了,用is_same来判断对不对,我们先写判断的程序吧:

#include <iostream>

int main()
{
    std::cout<< std::is_same<int,long>::value <<std::endl;
    std::cout<< std::is_same<int,int>::value <<std::endl;
    std::cout << "Hello world" << std::endl;
    return 0;
}

之后,只需要把我们的add_const_ref<int>const int & 判断是否相等,并判断add_const_ref<int&>int是否相等就可以了。
我理解的话,这个题目应该就是锻炼一下对于std里面一堆基础元函数组件的使用,并且了解元函数编写的基本方法。
代码如下:

#include <iostream>

/* template<typename T> */
/* T& add_const_ref() */
/* { */
/*     return (std::is_reference<T>::value)?T:T const & */
/* } */
template<bool,typename T>
struct add_const_ref_detail{};

template<typename T>
struct add_const_ref{
    typedef typename add_const_ref_detail<std::is_reference<T>::value,T>::type type;
};

template<typename T>
struct add_const_ref_detail<true,T>
{
    //如果是引用,就返回T
    typedef typename std::remove_reference<T>::type type;

};

template<typename T>
struct add_const_ref_detail<false,T>
{
    //如果不是,就返回T const &
    typedef typename std::add_lvalue_reference<typename std::add_const<T>::type>::type type;

};
int main()
{
    std::cout<< std::is_same<add_const_ref<int>::type,const int &>::value <<std::endl;

    std::cout<< std::is_same<add_const_ref<int&>::type,int>::value <<std::endl;
    std::cout << "Hello world" << std::endl;
    return 0;
}

这里先声明了一个泛化的类模板add_const_ref_detail,之后通过判断T是不是引用类型来导流到具体的特化细节处理上,即后面的add_const_ref_detail<true,T>add_const_ref_detail<false,T>,这是元函数编写的基本套路。
另一个细节是这里的add_lvalue_referenceadd_const的先后问题,通过查看相关说明,对于add_const是有这样的说明的:

Creates a lvalue or rvalue reference type of T.

  1. If T is a function type that has no cv- or ref- qualifier or an object type, provides a member typedef type which is T&. If T is an rvalue reference to some type U, then type is U&. Otherwise, type is T.
  2. If T is a function type that has no cv- or ref- qualifier or an object type, provides a member typedef type which is T&&, otherwise type is T.
    The behavior of a program that adds specializations for any of the templates described on this page is undefined.

也就是说,这里如果先加引用再加const的话,const无效,因此,应该先加const后加引用。

<2020年12月23日 持续更新ing>
我发现我年轻了,我没有闪,搜到了一个大佬的文章大佬在此
这个题目我可能搞得复杂了,对于是不是引用这个事情,可以不用std::is_reference的,特化过的模板在匹配时候会自动匹配到这个条件,至于其他的也可以简单点:

#include <iostream>
template<typename T>
struct add_const_ref
{
    typedef T const& type;
};

template<typename T>
struct add_const_ref<T&>
{
    typedef T type;
};

int main()
{
    std::cout<< std::is_same<add_const_ref<int>::type,const int &>::value <<std::endl;

    std::cout<< std::is_same<add_const_ref<int&>::type,int>::value <<std::endl;
    std::cout << "Hello world" << std::endl;
    return 0;
}

2-1

编写一个三元元函数replace_type<c,x,y>,它接收一个任意的复合类型c作为其第一个参数,并将c中出现的所有type x替换为y:

typedef replace_type< void*, void, int >::type t1; // int*
typedef replace_type<
int const*[10]
, int const
, long
>::type t2; // long* [10]
typedef replace_type<
char& (*)(char&)
, char&
, long&
>::type t3; // long& (*)(long&)

你可以将所操作的函数类型限制为具有少于两个参数的函数。

这个比较复杂,不过最让人束手无策的应该是那句话将c中出现的所有type x替换为y,不过从例子中看,这里的c中的x一定是以或者指针、或者数组、或者加入c-v修饰符、或者二维数组之类一系列修饰起来的,因此,如何get到每种情况,并进行一一替换呢?
我们这里把问题缩小一点,将c中出现的x替换为y,x只会以指针、const、引用三种形式出现
那么判断程序可以写出来了:

#include <iostream>
template<typename C, typename X, typename Y>
struct replace_type
{
    typedef C type;
};


int main()
{
    std::cout<< std::is_same<replace_type<int*, int, char>::type,char*>::value <<std::endl;
    std::cout<< std::is_same<replace_type<int const, int, char>::type,char const>::value <<std::endl;
    std::cout<< std::is_same<replace_type<int&, int, char>::type,char&>::value <<std::endl;
    std::cout << "Hello world" << std::endl;
    return 0;
}

如何判断是带指针、const、引用和函数类型呢?这里使用了模板特化的方式来route到对应的操作上:

#include <iostream>
template<typename C, typename X, typename Y>
struct replace_type
{
    typedef C type;
};

template<typename C, typename Y>
struct replace_type<C*, C, Y>
{
    typedef typename std::add_pointer<Y>::type type;
};

template<typename C, typename Y>
struct replace_type<C const, C, Y>
{
    typedef typename  std::add_const<Y>::type type;
}; 

template<typename C, typename Y>
struct replace_type<C (*)(C), C, Y>
{
    template<typename T = Y(*)(Y)>
    struct MyStruct
    {
        typedef typename T type;
    };
    typedef typename MyStruct<>::type type;
};

template<typename C, typename Y>
struct replace_type<C&, C, Y>
{
    typedef typename std::add_lvalue_reference<Y>::type type;
};
int main()
{
    std::cout << std::is_same<replace_type<int*, int, char>::type, char*>::value << std::endl;
    std::cout << std::is_same<replace_type<int const, int, char>::type, char const>::value << std::endl;
    std::cout << std::is_same<replace_type<int&, int, char>::type, char&>::value << std::endl;
    //added at 2022-10-2
    std::cout << std::is_same<replace_type<char& (*)(char&), char&, long&>::type, long& (*)(long&)>::value << std::endl;
    std::cout << "Hello world" << std::endl;
    return 0;
}

2-2

实现一个带检查版本的static_cast,用于将指向多态对象的指针向下转型:

template <class Target, class Source>inline Target polymorphic_downcast(Source* x)
{
assert( dynamic_cast<Target>(x) == x );
return static_cast<Target>(x);
}

在发行版的软件中,assertion消失并且polymorphic_downcast可以和简单的static_cast一样高效。
现在,我们使用该type traits设施来编写一个模板实现品,使其既可接收指针参数也可接收引用参数:

struct A { virtual ~A() {} };
struct B : A {};
B b;
A* a_ptr = &b;
B* b_ptr = polymorphic_downcast<B*>(a_ptr);
A& a_ref = b;
B& b_ref = polymorphic_downcast<B&>(a_ref);

这个题目要解决的问题是将引用route到只接受指针的函数版本中,这个就很好办了。

#include <iostream>
#include <cassert>
struct A { virtual ~A() {} };
struct B : A {};

template <class Target, class Source>
inline Target polymorphic_downcast(Source* x)
{
    assert( dynamic_cast<Target>(x) == x );
    return static_cast<Target>(x);
}

template <class Target, class Source>
//route to ref func
inline Target polymorphic_downcast(Source& x)
{
    assert( dynamic_cast<typename std::add_pointer<typename std::remove_reference<Target>::type>::type >(&x) == &x );
    return static_cast<Target>(x);
}
int main()
{
    B b;
    A* a_ptr = &b;
    B* b_ptr = polymorphic_downcast<B*>(a_ptr);
    assert (b_ptr == a_ptr);
    A& a_ref = b;
    //route to ref func
    B& b_ref = polymorphic_downcast<B&>(a_ref);
    assert (&b_ref == &a_ref);
    std::cout << "Hello world" << std::endl;
    return 0;
}

如果想要简单一点,也可以这么写:

#include <iostream>
#include <cassert>
struct A { virtual ~A() {} };
struct B : A {};

template <class Target, class Source>
//route to ref func
inline Target polymorphic_downcast(Source& x)
{
    assert( dynamic_cast<typename std::remove_reference<Target>::type *>(&x) == &x );
    return static_cast<Target>(x);
}
int main()
{
    B b;
    A& a_ref = b;
    //route to ref func
    B& b_ref = polymorphic_downcast<B&>(a_ref);
    assert (&b_ref == &a_ref);
    std::cout << "Hello world" << std::endl;
    return 0;
}

Print data-types using Boost MPL.

答疑:为什么使用如下形式编译不过

template <class Target, class Source>
//route to ref func
inline Target polymorphic_downcast(Source& x)
{
    assert(dynamic_cast<Target>(x) == x);
    return static_cast<Target>(x);
}

提示信息: error C2676: 二进制“==”:“B”不定义该运算符或到预定义运算符可接收的类型的转换
答案:因为==对于指针类型是表意明确的,dynamic转换不兼容类型指针时候返回是空指针,可以这样判断,对于引用来说,总是返回带类型的引用(或者抛出异常),所以==的含义取决于他左边或者右边的变量类型,所以能不能编译过取决于类型本身的定义,比如下面就能编译过。


#include <iostream>
#include <cassert>
struct A { virtual ~A() {} };
struct B : A {
    bool operator==(const A&) { return true; }
};

template <class Target, class Source>
//route to ref func
inline Target polymorphic_downcast(Source& x)
{
    assert(dynamic_cast<Target>(x) == x);
    return static_cast<Target>(x);
}
int main()
{
    B b;
    A& a_ref = b;
    //route to ref func
    B& b_ref = polymorphic_downcast<B&>(a_ref);
    assert(&b_ref == &a_ref);
    std::cout << "Hello world" << std::endl;
    return 0;
}

然而,这样子依赖于类型本身的定义,肯定不是一个通用的cast函数。

2-3

实现 type_descriptor 模板,可以在流式处理时输出类的名字

// prints "int"
std::cout << type_descriptor<int>();
// prints "char*"
std::cout << type_descriptor<char*>();
// prints "long const*&"
std::cout << type_descriptor<long const*&>();

模板的参数可以认为只有char、short、int 和 long 四种类型的指针或const类型组合。

如果只有这四种类型的话,那就挨个特化std::osteam & operator << (std::osteam &, T)就可以了。
这里有两种等价的写法,各自含义不同,有一种经典的话——C++中的operator主要有两个作用,一是操作符的重载,一是操作符的转换std::osteam & operator << (std::osteam &, T)用的是重载,operator const char*用的是转换。这里的转换指的是隐式类型转换,可以和构造函数做一下区分,构造函数的隐式类型转换部分,使用一个其他的类型构造当前类的临时对象,这种转换必须有构造函数的支持;operator算子的隐式类型转换,使用当前对象去生成另一个类型的对象(正好与构造函数型相反),这种转换必须有operator算子的支持。

template <typename T>
struct type_descriptor{};

template <>
struct type_descriptor<char>
{
};
namespace std{
std::ostream& operator<<(std::ostream& rhs, type_descriptor<char> )
{
    rhs<<"char";
    return rhs;
}

};
template <>
struct type_descriptor<short>
{
    operator const char*()
    {
        return "short";
    }
};
int main()
{
    std::cout << type_descriptor<char>()<<std::endl;

    std::cout << type_descriptor<short>()<<std::endl;
    return 0;
}

这里我曾经尝试重载了operator std::string(),发现std::cout是报错的,想记录一下原因:
首先,std::cout输出内容时的过程:

  1. 查看std::ostream内部的std::ostream & operator<<(T)
  2. 查看全局函数std::ostream& operator<<(std::ostream& rhs, T )
  3. 向已有的operator<<匹配

值得注意的是,这里的std::string可以隐式类型转换为char* type_descriptor<short>可以隐式类型转换成std::string,但是 type_descriptor<short>不能连续两次隐式类型转换到char*,所以报错,编译失败。
而全局函数的重载或者直接隐式类型转换到char*就不会遇到这个问题的。

2-4

和 2-3 一样

2-5

伪语义描述:

// prints "array of pointer to function returning pointer to"
//        "char"
std::cout << type_descriptor< char *(*[])() >();

这个关键在于pointer to array of 这类如何描述,基础类型用上面的方法,这些of系列的操作可以通过模板特化的方式呈现出来:

template <typename T>
std::ostream& operator<<(std::ostream& os, type_descriptor_eng<T const> const&td)
{
    return os << "const " << type_descriptor<T>();
}
template <typename T>
std::ostream& operator<<(std::ostream& os, type_descriptor_eng<T&> const&)
{
    return os << "reference to " << type_descriptor<T>();
}
template <typename T>
std::ostream& operator<<(std::ostream& os, type_descriptor_eng<T*> const&)
{
    return os << "pointer to " << type_descriptor<T>();
}
template <typename T>
std::ostream& operator<<(std::ostream& os, type_descriptor_eng<T[]> const&)
{
    return os << "array of " << type_descriptor<T>();
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值