为什么默认模板参数只允许在类模板上使用?

问题描述 投票:0回答:5

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

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

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

c++ templates language-design c++98
5个回答
155
投票

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

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 风格的代码变得更加困难。


38
投票

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

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


18
投票

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

阿拉克:

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

斯特鲁斯特鲁普:

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 的缺陷报告所指出的那样,添加非推导参数对于任何人来说都太晚了,以至于无法意识到和/或真正意识到它使默认值变得有用。因此,当前的情况实际上是基于从未标准化的函数模板版本。


2
投票

在 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
投票

我使用的是下一个技巧:

假设您想要这样的功能:

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

我们可以看到不需要显式设置第二个参数。 也许这对某人有用。

© www.soinside.com 2019 - 2024. All rights reserved.