这一章的主题是类型识别,所以习题也都是围绕类型识别来设置的。
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_reference
和add_const
的先后问题,通过查看相关说明,对于add_const
是有这样的说明的:
Creates a lvalue or rvalue reference type of T.
- 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.
- 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
输出内容时的过程:
- 查看
std::ostream
内部的std::ostream & operator<<(T)
- 查看全局函数
std::ostream& operator<<(std::ostream& rhs, T )
- 向已有的
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>();
}