模板小计
1.
decltype 返回一个数据的类型
decltype(1)就相当于int
decltype(1) a;->int a;//这是等价的
decltype(23.11) b;->double b;//这是等价的
std::declval与decltype一起使用,作用就是调用一个结构体或是类的方法,但是不用实例化,直接用类名调用即可
不可以单独使用
struct Test
{
int fun()
{
return 453;
}
};
decltype(std::declval<Test>().fun()) a = 1;
//上面等价于
int a = 1;
std::declval<Test>().fun()就是调用Test中的fun函数,可以避免实例化一个Test
2.0
error C2665: 'std::to_string': none of the 9 overloads could convert all the argument types
while trying to match the argument list '(const T)'
编写模板编译的时候如果报上面的错误,是因为调用了std::to_string()的方法,你的模板在展开的时候,发现你传入的T参数是一个std::to_string()无法调用的类型。如下面红色部分。
string a;
SetValue(a);
void SetValue(const T& svalue)
{
string repstr;
if (std::is_arithmetic<T>::value)
{
repstr = std::to_string(svalue);
}
else
{
repstr = "'" + svalue + "'";
}
for (auto iter = m_sqlstr.begin(); iter != m_sqlstr.end(); iter++)
{
if (*iter == '?')
{
m_sqlstr.replace(iter, iter + 1, svalue);
break;
}
}
}
有一种修改就是把这个不符合模板里面调用方法的类型重载出来
void SetValue(const string& svalue)
{
string repstr;
repstr = "'" + svalue + "'";
for (auto iter = m_sqlstr.begin(); iter != m_sqlstr.end(); iter++)
{
if (*iter == '?')
{
m_sqlstr.replace(iter, iter + 1, svalue);
break;
}
}
}
template<typename T>
void SetValue(const T& svalue)
{
string repstr;
if (std::is_arithmetic<T>::value)
{
repstr = std::to_string(svalue);
}
else
{
repstr = "'" + svalue + "'";
}
for (auto iter = m_sqlstr.begin(); iter != m_sqlstr.end(); iter++)
{
if (*iter == '?')
{
m_sqlstr.replace(iter, iter + 1, svalue);
break;
}
}
}
上面红色的就是增加的新的方法,避免了std::to_string中不符合要求的类型
2.1
引用 https://stackoverflow.com/questions/16591519/argument-deduction-with-template-member-function-and-non-template-overload
template< class T > void add_to_header( const std::string &key, const T &val ) { add_to_header( key, std::to_string( val ) ); } virtual void add_to_header( const std::string &key, const std::string &val );
作者碰到了一个问题,如下
instance.add_to_header( "Example1", 4 ); // successful instance.add_to_header( "Example2", std::string( "str val" ) ); // successful instance.add_to_header( "Example3", "Not fun" ); // error - none of the 9 overloads could convert all the argument types
原因和上面一样:
第一行调用是(const char*, int),匹配的是模板,没问题
第二行调用是(const char*, string),匹配的是函数,没问题
第三行调用是(const char*, const char*),因为第二个参数const char*更能直接匹配模板的T,函数的const string& val则需要隐式转换,所以匹配的是模板。那么模板里面调用std::to_string(const char*)肯定是不对的,所以报错了。
如果想解决这个问题,可以在定义一个函数如下,把(const char*, const char*)从模板中抽离出来
void add_to_header(const char* key, const char* val);
提问下面有人提出了更巧妙的方法
void add_to_header(const std::string &key, const std::string &val); template<typename T> auto add_to_header(const std::string &key, const T &val) -> decltype(std::to_string(val), void()) { add_to_header(key, std::to_string(val)); }
上面定义了一个模板,然后通过c++的Substitution failure is not an error (SFINAE)技术来区分使用哪种调用。
如果调用了(const char*, const char*)那么模板在展开时,由于std::to_string(val)就会报错,那么模板就不会展开,那么就会调用上面的函数.
但是第二个方法我这边没有编译通过。
2.3
Substitution failure is not an error (SFINAE)
参考 https://stackoverflow.com/questions/31017032/decltype-with-two-parameters-decltypea-b-for-function-return-type
上面讲到了这个技术,这个具体是用来做什么的呢,翻译过来就是展开的时候的失败不是错误。
template<class C, class F> auto test(C c, F f) -> decltype((void)(c.*f)(), void()) { std::cout << "member function\n"; } template<class C> void test(C c, int) { std::cout << "int\n"; } struct X { int f() { return 42; } double g(int) { return 3.14; } }; int main() { X x; test(x, &X::f); // ok - outputs "member function\n" // test(x, &X::g); // CT error - g needs an argument test(x, 99); // ok - outputs "int\n" }
这个例子很清晰的介绍了这种技术
在man中第一个调用中,传入了x和类X的函数指针,那么第一个模板是可以正常展开的,所以就调用了第一个模板
第二行调用,发现没有匹配的模板,就会报错
第三行调用发现第一个模板无法匹配,因为99是个整数,没有f()函数,所以展开失败,根据这个技术,失败不是错误,所以继续寻找下一个模板,然后发现可以匹配,那么就调用了第二个模板
参考 https://www.jianshu.com/p/45a2410d4085
long multiply(int i, int j) { return i * j; } template <class T> typename T::multiplication_result multiply(T t1, T t2) { return t1 * t2; } int main(void) { multiply(4,5); }
模板和宏基本一样,不检测类型,在编译的时候才展开,上面的例子就是
multiplication_result是一个非法的参数,所以main调用的时候展开失败,那么就调用了上面的函数
template<typename T> struct has_no_destroy { template<typename C> static char test(decltype(&C::no_destroy)); template<typename C> static int32_t test(...); const static bool value = sizeof(test<T>(0)) == 1; }; // 其作用就是用来判断是否有 no_destroy 函数 struct A { }; struct B { void no_destroy(){} }; struct C { int no_destroy; }; struct D : B { }; void testNoDestroy() { printf("%d\n",has_no_destroy<A>::value); printf("%d\n",has_no_destroy<B>::value); printf("%d\n",has_no_destroy<C>::value); printf("%d\n",has_no_destroy<D>::value); }
作者提供了另外一个muduo的例子(muduo作者代码中介绍的是源于 http://stackoverflow.com/questions/1966362/sfinae-to-check-for-inherited-member-functions 查资料是发现维基百科中介绍的也很详细 并且与stackoverflow中例子类似https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error),A传入到结构体中,匹配模板的时候发现没有no_destroy函数,所以调用的是返回值为int32_t的方法,所以计算size_of不是一个字节,所以是false,其他几个都有这个函数,就调用的返回值是char的方法,所以是true
但是test两个函数都没有实现,是没问题的,我实现了发现也并没有调用
3.
c++模板(template)中auto和->(箭头符号)的作用
参考 https://stackoverflow.com/questions/22514855/arrow-operator-in-function-heading
template <typename T, typename T1> auto compose(T a, T1 b) -> int
{
return a + b;
}
就是返回类型是不定的,所以前面是auto,然后具体返回类型是什么,由->后面指定,比如上面的代码,返回一个int。
如果是上面使用,还不如把auto直接换成int,但是还可以增加新的用法。
template <typename T, typename T1> auto compose(T a, T1 b) -> decltype(a+b)
{
return a + b;
}
这样就是根据a+b的类型返回,上面讲了decltype是获取这个数据的类型。
如果是这个例子,这么麻烦的用法与直接用T定义返回值没有什么优势,但是decltype里面还可以加方法,这样的话就可以实现更复杂的用法,仅仅是用T来做为返回值是无法达到的。
C++14后增加了更简单的用法可以把->后面的省略掉。
template <typename T, typename T1> auto compose(T a, T1 b)
{
return a + b;
}
4.
在调用模板的方法时,有时候需要指定增加tamplate关键字,因为有时候会引起起义导致程序不知道应该怎么运行。
参考 https://www.zhihu.com/question/37990298
template<class T>
int f(T& x)
{
return x.template convert<3>(pi);
}
上面的代码如果没有增加template就有可能被理解为
return ((x.convert) < 3) > (pi);
5.
template using的作用就是用模板定义别名。下面的例子中,两句话是等价的
template<typename T> using tp = T * ;
tp<int> a;
int* a;
6.
std::is_same::value
template <class T, class U> struct is_same;
这个模板的作用是用来判断两个类型T和U是不是同样的变量。
is_same<int, int>::value就是true,这样可以在模板中对特殊类型进行判断做特殊的处理
7.
形参包 Parameter pack ...
待续