C++17支持类模板类型推导(class template argument deduction,在下面的文章中,我叫做CTAD)。
而我们在很久之前就有了template argument deduction,但是只能用于函数,这多少有点不公平。
此篇博客的内容来自cppcon2018_CTAD 。
//before C++17
std::pair<int, string> p1(3, "string");
auto p2 = make_pair(3, "string");
//deduction pair
//C++17 or late
std::pair p3(3, string("hello")); // nice !
template <class T, class U>
struct pair{
T first;
U second;
pair(const T& first_, const U& second_)
:first(first_), second(second_)
{}
pair(T&& first_, U&& second_)
:first(std::forward<T>(first_))
,second(std::forward<U>(second_))
{}
//...
};
std::pair p(3, string("hello")); //how does it work ?
第一步:
template <class T, class U> //来自pair类
pair(const T& first_, const U& second_)
:first(first_), second(second_)
{}
template <class T, class U> //来自pair类
pair(T&& first_, U&& second_) //右值引用,不是转发引用
:first(std::forward<T>(first_))
,second(std::forward<U>(second_))
{}
第二步:
tuple t1{3, 3.14, "string"};
auto t2 = make_tuple(1, 1.11, "hello"); //该退休了。
zero or all
std::tuple t1<std::string>{"string", 3, 3.14}; //error !
auto t2 = make_tuple<std::string>("string", 3, 3.14); // ok !
std::vector v1{1, 2, 3, 4}; //ok, vector
std::vector v2{1, 2 ,3 ,4.0f}; // error !
std::vector<int> v1{3}; //一个元素,3
std::vector<int> v2(3); //3个元素,0,0,0
std::vector v1{3}; //ok,一个元素,3
std::vector v2(3); //error !
std::vector range{1, 2, 3, 4};
std::vector v(range.begin(), range.end()); //ok, vector
template <class T>
class vector{
template <class Iter>
vector(Iter first, Iter last);
//...
};
template <class T, class Iter>
vector(Iter first, Iter last);
template <class T>
class vector{
template <class Iter>
vector(Iter first, Iter last);
//...
};
template <class Iter> //deduction guides
vector(Iter, Iter)->vector<typename iterator_traits<Iter>::value_type>;
std::vector v1(range.begin(), range.end()); //ok, 可以推导
std::vector v2(1, 2); //不会调用关于迭代器的构造函数,因为int没有iterator_traits
顺序很重要:
template <class T>
class vector{
template <class Iter>
vector(Iter first, Iter last);
//...
};
std::vector range{1, 2, 3, 4};
std::vector v(range.begin(), range.end()); //error !
template <class Iter> //deduction guides
vector(Iter, Iter)->vector<typename iterator_traits<Iter>::value_type>;
陷阱2:花括号有优先级。
vector range{1, 2, 3};
vector v1(range.begin(), range.end()); //vector
vector v2{range.begin(), range.end()}; //vector::iterator>
other containers:
vector range{1, 2, 3, 4};
list l(range.begin(), range.end()); //list
forward_list fl(range.begin(), range.end()); //forward_list
deque d(range.begin(), range.end());
set:
set s1{1, 2, 3}; //ok, set
set s2(s1.begin(), s1.end()); //ok, set
set s3({1, 2, 3}, [](int lhs, int rhs)->bool{
return lhs > rhs ;
}); //ok
set s4(s1.begin(), s1.end(), [](int lhs, int rhs){
return lhs > rhs;
});
陷阱3, map:
std::map m{{1, 3.14}, {2, 6.66}}; // error
std::map m{std::pair{1, 3.14}, std::pair{2, 6.66}}; //ok,map
CTAD 和拷贝构造函数
vector v{1, 2, 3};
vector v1{v}; //ok
list l{1, 2, 3};
list l1{l};
copy wins:
vector v{1, 2, 3};
vector v1{v}; //调用拷贝构造
vector v2{v, v1}; //调用初始化列表的构造函数
locks && mutexes
before C++17
std::shared_timed_mutex mtx;
std::lock_guard<std::shared_timed_mutex> lock(mtx);
//now
std::lock_guard lock(mtx); //CTAD !
//or,
std::scoped_lock lock(mtx); //more better !
CTAD && 无参构造
std::less<>{}; //before C++17
std::less{}; //now
CTAD && more
std::optional o{10}; // optional
std::complex c{1.0, 3.14}; // complex
std::complex c{1, 2}; //推导出complex
智能指针:
class Person{
public:
Person(std::string name, int id);
//...
};
std::shared_ptr sp(new Person("zzh", 1)); // error
auto sp1 = make_shared<Person>("zzh", 1); //ok
std::unique_ptr up(new Person("zzh", 1)); // error
auto up = make_unique<Person>("zzh", 1); //ok
std::unique_ptr up(new int[10]); // 推断出什么?
std::unique_ptr<int[]> up(new int[10]); //ok !
type_identity技法:
template <class T>
class my_smart_ptr{
public:
my_smart_ptr(T* ptr)
:ptr_(ptr){}
//...
T* ptr_;
};
my_smart_ptr msp(new int[10]); //compiler, bad
template <class T>
struct type_identity{
using type = T;
};
template <class T>
using type_identity_t = typename type_identity<T>::type;
template <class T>
class my_smart_ptr{
public:
my_smart_ptr(type_identity_t<T>* ptr)
:ptr_(ptr){}
//...
T* ptr_;
};
my_smart_ptr msp(new int[10]); //error ,not compile
现在该调用不会编译。
当编译器尝试去编译时,采取CTAD,但是它发现了type_identity_t也是一个模板,CTAD不会去实例化另外的模板来推断当前的模板。所以,编译器会停下来,告诉你“sorry,type_identity_t是个模板,我无法实例化这个来推断T的类型,所以,error。”
这就是我们常说的“non-deduction情况”(此术语来自《C++template》一书)。
显式的调用则会起效果,因为type_identity_t< T >就是T。
type_identity是相当厉害的手法,它还适用于以下情况:
template <class T> //普通函数
void fun(type_identity_t<T> t){}
fun(1); //error !
fun<int>(1) // ok!
template <class T, class U> //普通函数
void fun(type_identity_t<T> t, U d){}
fun(1, 3.14); //error !
fun<int>(1, 3.14) // ok!
fun<int, double>(1, 3.14); //ok !
typedef手法:
template <class T>
class my_smart_ptr{
public:
using pointer = T*;
my_smart_ptr(pointer ptr)
:ptr_(ptr){}
//...
T* ptr_;
};
my_smart_ptr msp(new int[10]); //bad, compile
template <class T>
class my_smart_ptr{
public:
using pointer = add_pointer_t<T>*;
my_smart_ptr(pointer ptr)
:ptr_(ptr){}
//...
T* ptr_;
};
my_smart_ptr msp(new int[10]); //ok, not compile
template member手法:
template <class T>
class my_smart_ptr{
public:
template <class U> //模板成员
my_smart_ptr(U* ptr)
:ptr_(ptr){}
//...
T* ptr_;
};
my_smart_ptr msp(new int[10]); //ok, not compile
std::function
void fun();
struct Test{
void operator(){}
};
std::function f1(&fun);
std::function f2(Test());
std::function f3([](){});
template <class Ret, class ... Args> //函数指针
function(Ret(*)(Args...))->function<Ret(Args)>;
template <class T>
class vector{
template <class Iter>
vector(Iter first, Iter last);
vector(size_t n, const T& value = T());
//...
};
template <class Iter> //deduction guides
vector(Iter, Iter)->vector<typename iterator_traits<Iter>::value_type>;
vector v(1, 2);
让我们更细致的考虑:
template <class Iter>
vector(Iter first, Iter last)
->vector<
enable_if_t<
is_base_if_v<
input_iterator_tag,
typename iterator_traits<Iter>::iterator_catagory>,
typename iterator_traits<Iter>::value_type>>;
std::array
template <class T, size_t N>
class array{
T data_[N];
//...
};
array arr{1, 2, 3, 4}; //ok, array;
template <class T, class ... Args>
array(T, Args...)->array<T, 1 + sizeof...(Args)>;
array arr{1, 2, 3, 4}; //well !
array arr1{1, 2.0}; // bad !
template <class T, class...Args>
array(T, Args...)->array<
enable_if_t<
conjunction_v<is_same<T, Args>...>, T>, 1 + sizeof...(Args)>;
or
template <class T, class...Args>
array(T, Args...)->array<
enable_if_t<(is_same_v<T, Args> && ...), T>, 1 + sizeof...(Args)>;
trap:
template <class T>
struct Foo{};
template <>
struct Foo<int>{
Foo(int){}
};
Foo f(1); //error !
template <class T>
struct Foo{
Foo(T){}
};
template <>
sruct Foo<int>{
Foo(const double&){} //这个版本将会被调用
};
Foo f(1); //Foo
在CTAD的第一阶段,f被推导为Foo< int >。但是在第二步,编译器已经知道了你的类型。他会发现Foo有对int的特化模板,所以它会调用特化模板的const double&来初始化f !!
建议,特化模板的构造函数的标签,也要与主模板保持一致。
auto* p = new std::pair{3, 3.14}; // new表达式, ok
std::mutex mtx;
auto lock = lock_guard(mtx); //函数式的类型转换, ok
std::pair p{3, 3.14};
std::pair& pref{p}; //error !
auto& pref{p}; //ok, 使用auto
auto uptr1 = std::make_unique<Person>("zzh", 1);
//ok, unique_ptr
std::unique_ptr uptr2{std::move(uptr1)};
//ok, shared_ptr
std::shared_ptr sptr2{std::move(uptr2)};
//...
//deduction guide:
template <class T, class Deleter>
shared_ptr(std::unique_ptr<T, Deleter>)->shared_ptr<T>;
// constructor:
template <class T, class Deleter>
shared_ptr(std::unique_ptr<T, Deleter>&& uptr);
deduction guides可以不是模板:
template <class T>
struct Foo{
T name_;
Foo(T name) : name_(name) {}
//...
};
Foo f("zzh"); //推导出Foo,但是我们想要Foo
template <class T>
struct Foo{
T name_;
Foo(T name) : name_(name) {}
//...
};
Foo(const char*)->Foo<string_view>; //非模板
Foo f("zzh"); //推导出Foo;
1. aggregates需要显式的deduction guide。
template <class T>
struct Name{
T first, last;
};
Name<std::string> name{"zz", "zh"}; //ok,初始化
Name name{"zz", "zh"}; // error !
template <class T>
struct Name{
T first, last;
};
template <class T>
Name(T, T)->Name<T>; //这是必须的
Name name("zz", "zh"); //ok
Name name("zz", "zh"); //error!
经过我的测试,在C++20中,VS和GCC都支持了aggregate的小括号初始化方式,但是clang仍然不支持。这三者都不支持没有显式deduction guides的花括号初始化。
2,CTAD不支持模板别名
template <class T>
struct Foo{
Foo(T t) {
cout << t;
}
};
template <class T>
using Foo_ = Foo<T>; //template alias
int main() {
Foo f(1); //ok
Foo_ f1(1); //error
return 0;
}
3,CTAD看不到继承的构造函数,你需要显式提供deduction guides。
不支持部分的CTAD,C++20仍不支持。
std::tuple<std::string> t{"zzh", 1, 3.14}; //error