函数模板的默认模板参数


187

为什么只在类模板上允许使用默认模板参数?为什么我们不能在成员函数模板中定义默认类型?例如:

struct mycclass {
  template<class T=int>
  void mymember(T* vec) {
    // ...
  }
};

相反,C ++强制仅在类模板上允许使用默认模板参数。


8
+1这确实是一个棘手的问题。
AraK 2010年

1
对于前三个发布的答案,请考虑以下示例:struct S { template <class R = int> R get_me_R() { return R(); } };无法从上下文中推导出template参数。
AraK 2010年

3
好问题。已经有3个人回答说“没有道理”,他们总体上都是错的。函数模板参数并不总是可以从函数调用参数中扣除。例如,如果允许,则可以编写template <int N = 1> int &increment(int &i) { i += N; return i; },然后编写increment(i);increment<2>(i);。照原样,我必须写increment<1>(i);
史蒂夫·杰索普

实际上,我和AraK的示例都可以通过重载来处理。我认为,litb不能,因为可能推导或指定了template参数。
史蒂夫·杰索普

3
@Steve:缺少的分号实际上是对B的新的EOL运算符重载。Stavtrup的“ C ++空格重载”发表于《面向对象程序设计杂志》,1992年4月1日。(www2.research.att.com/~bs/ papers.html

Answers:


148

给出默认模板参数是有意义的。例如,您可以创建一个排序函数:

template<typename Iterator, 
         typename Comp = std::less<
            typename std::iterator_traits<Iterator>::value_type> >
void sort(Iterator beg, Iterator end, Comp c = Comp()) {
  ...
}

C ++ 0x将它们引入C ++。请参见Bjarne Stroustrup的以下缺陷报告:功能模板的默认模板参数及其说明

禁止将默认模板参数用作函数模板是在将独立函数视为第二类公民并要求从函数参数推导而不是指定所有模板参数的时代遗留下来的错误信息。

该限制通过不必要地使独立功能不同于成员函数而严重限制了编程风格,从而使编写STL风格的代码变得更加困难。


@Arman,缺陷报告链接包含对C ++ 0x工作草案和讨论所做的更改。既不推导也不明确指定的参数是从默认参数获得的。GCC4.4在C ++ 0x模式下支持函数模板的默认参数。
Johannes Schaub-litb 2010年

4
与问题或答案无关,但在上个星期六的会议之后,Herb Sutter调用了即将到来的标准C ++ 11。我今天刚刚读它,感觉就像分享:) herbsutter.wordpress.com/2010/03/13/...
大卫·罗德里格斯- dribeas

和强制性的后续问题...什么时候可以预期到其他编译器:)
Jamie Cook 2010年

@ JohannesSchaub-litb我遇到了同样的问题:无法在模板函数中指定默认类型。我已经解决了默认类型上函数的显式实例化(double以我为例)。也许这不是“一般性的”,但是这种做法有什么缺点吗?谢谢。
JackOLantern

以下代码无法编译,并带有诸如以下的错误error: invalid conversion from ‘int’ to ‘int*’提示:`#include <array> #include <algorithm> #include <functional> template <typename Iterator,typename Comp = std :: less <Iterator>> void my_sort(迭代器求,迭代器结束,Comp c = Comp()){std :: sort(beg,end,c); } int main(){std :: array <int,5> ar {5,2,21,7,4}; my_sort(ar.begin(),ar.end()); }
卢克·彼得森

36

引用C ++模板:完整指南(第207页):

最初将模板添加到C ++语言时,显式函数模板参数不是有效的构造。始终必须从调用表达式中推导函数模板参数。结果,似乎没有令人信服的理由允许默认函数模板参数,因为默认值总是会被​​推导的值覆盖。


简单明了:)
InQusitive 2014年

17

到目前为止,功能模板的所有默认模板参数提供示例都可以通过重载完成。

阿拉克:

struct S { 
    template <class R = int> R get_me_R() { return R(); } 
};

可能:

struct S {
    template <class R> R get_me_R() { return R(); } 
    int get_me_R() { return int(); }
};

我自己的:

template <int N = 1> int &increment(int &i) { i += N; return i; }

可能:

template <int N> int &increment(int &i) { i += N; return i; }
int &increment(int &i) { return increment<1>(i); }

小玩意儿:

template<typename Iterator, typename Comp = std::less<Iterator> >
void sort(Iterator beg, Iterator end, Comp c = Comp())

可能:

template<typename Iterator>
void sort(Iterator beg, Iterator end, std::less<Iterator> c = std::less<Iterator>())

template<typename Iterator, typename Comp >
void sort(Iterator beg, Iterator end, Comp c = Comp())

Stroustrup:

template <class T, class U = double>
void f(T t = 0, U u = 0);

可能:

template <typename S, typename T> void f(S s = 0, T t = 0);
template <typename S> void f(S s = 0, double t = 0);

我用以下代码证明了这一点:

#include <iostream>
#include <string>
#include <sstream>
#include <ctype.h>

template <typename T> T prettify(T t) { return t; }
std::string prettify(char c) { 
    std::stringstream ss;
    if (isprint((unsigned char)c)) {
        ss << "'" << c << "'";
    } else {
        ss << (int)c;
    }
    return ss.str();
}

template <typename S, typename T> void g(S s, T t){
    std::cout << "f<" << typeid(S).name() << "," << typeid(T).name()
        << ">(" << s << "," << prettify(t) << ")\n";
}


template <typename S, typename T> void f(S s = 0, T t = 0){
    g<S,T>(s,t);
}

template <typename S> void f(S s = 0, double t = 0) {
    g<S,double>(s, t);
}

int main() {
        f(1, 'c');         // f<int,char>(1,'c')
        f(1);              // f<int,double>(1,0)
//        f();               // error: T cannot be deduced
        f<int>();          // f<int,double>(0,0)
        f<int,char>();     // f<int,char>(0,0)
}

打印的输出与对f的每次调用的注释匹配,并且注释掉的调用无法按预期编译。

因此,我怀疑默认模板参数“不需要”,但可能仅在与默认函数参数“不需要”相同的意义上。正如Stroustrup的缺陷报告所表明的那样,添加非推导参数对于任何人来说都太迟了,以至于没有人意识到和/或真正意识到它使默认设置有用。因此,当前的情况实际上是基于从来都不是标准功能模板的版本。


@Steve:所以鸡蛋跑得比鸡肉快?:)很有趣。谢谢。
阿尔曼2010年

1
可能只是其中之一。C ++标准化过程在某种程度上运行缓慢,因此人们有时间意识到变更在标准中其他地方带来的机会或困难。希望在执行标准草案的人们遇到矛盾或模棱两可时遇到困难。允许以前不允许的事情的机会,取决于想要编写代码的人,注意到它不再需要是非法的……
Steve Jessop 2010年

2
还有一个适合您:template<typename T = void> int SomeFunction();。这里从不使用template参数,实际上从不调用该函数;所指的唯一位置是decltype或中sizeof。该名称故意与另一个函数的名称匹配,但实际上它是一个模板,这意味着编译器将首选自由函数(如果存在)。在SFINAE中使用这两者来提供缺少函数定义的默认行为。
汤姆(Tom)

4

在Windows上,使用所有版本的Visual Studio,您都可以将此错误(C4519)转换为警告或将其禁用,如下所示:

#ifdef  _MSC_VER
#pragma warning(1 : 4519) // convert error C4519 to warning
// #pragma warning(disable : 4519) // disable error C4519
#endif

在这里查看更多详细信息。


1
请注意,尽管这确实禁用了“只允许在类模板上使用默认模板参数”消息,但实际上并没有使模板实例化过程使用提供的值。这需要VS2013(或已完成C ++ 11缺陷226“函数模板的默认模板参数”的任何其他编译器)
puetzk 2015年

1

我使用的是下一个技巧:

假设您要具有以下功能:

template <typename E, typename ARR_E = MyArray_t<E> > void doStuff(ARR_E array)
{
    E one(1);
    array.add( one );
}

您将不会被允许,但我会采取另一种方式:

template <typename T>
struct MyArray_t {
void add(T i) 
{
    // ...
}
};

template <typename E, typename ARR_E = MyArray_t<E> >
class worker {
public:
    /*static - as you wish */ ARR_E* parr_;
    void doStuff(); /* do not make this one static also, MSVC complains */
};

template <typename E, typename ARR_E>
void worker<E, ARR_E>::doStuff()
{
    E one(1);
    parr_->add( one );
}

因此,您可以这样使用它:

MyArray_t<int> my_array;
worker<int> w;
w.parr_ = &arr;
w.doStuff();

如我们所见,无需显式设置第二个参数。也许对某人有用。


这绝对不是答案。
小狗

@deadmg-您能解释为什么吗?我们并不是所有的C ++模板专家。谢谢。
凯夫

这是一个很好的解决方法,但是并不能涵盖您可能需要的所有情况。例如,如何将其应用于构造函数?
Tiberiu Savin 2013年

@TiberiuSavin-如果我对您的理解正确,那么您可以这样做:template <typename E,typename ARR_E> worker <E,ARR_E> :: worker(ARR_E * parr){parr_ = parr; }。然后像这样使用它:worker <int> w2(&my_array);
alariq 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.