函数模板的默认模板参数

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

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

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

76592 次浏览

It makes sense to give default template arguments. For example you could create a sort function:

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 introduces them to C++. See this defect report by Bjarne Stroustrup: Default Template Arguments for Function Templates and what he says

The prohibition of default template arguments for function templates is a misbegotten remnant of the time where freestanding functions were treated as second class citizens and required all template arguments to be deduced from the function arguments rather than specified.

The restriction seriously cramps programming style by unnecessarily making freestanding functions different from member functions, thus making it harder to write STL-style code.

To quote C++ Templates: The Complete Guide (page 207):

When templates were originally added to the C++ language, explicit function template arguments were not a valid construct. Function template arguments always had to be deducible from the call expression. As a result, there seemed to be no compelling reason to allow default function template arguments because the default would always be overridden by the deduced value.

So far, all the proffered examples of default template parameters for function templates can be done with overloads.

AraK:

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

could be:

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

My own:

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

could be:

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

litb:

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

could be:

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);

Could be:

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

Which I proved with the following code:

#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)
}

The printed output matches the comments for each call to f, and the commented-out call fails to compile as expected.

So I suspect that default template parameters "aren't needed", but probably only in the same sense that default function arguments "aren't needed". As Stroustrup's defect report indicates, the addition of non-deduced parameters was too late for anyone to realise and/or really appreciate that it made defaults useful. So the current situation is in effect based on a version of function templates which was never standard.

What I use is next trick:

Lets say you want to have function like this:

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

You will not be allowed, but I do next way:

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 );
}

So this way you may use it like this:

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

As we can see no need to explicitly set second parameter. Maybe it will be useful for someone.

On Windows, with all versions of Visual Studio you can convert this error (C4519) to a warning or disable it like so:

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

See more details here.