Аргументы шаблона по умолчанию для шаблонов функций

187

Почему аргументы шаблона по умолчанию разрешены только в шаблонах классов? Почему мы не можем определить тип по умолчанию в шаблоне функции-члена? Например:

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

Вместо этого C ++ заставляет использовать аргументы шаблона по умолчанию только для шаблона класса.

Arman
источник
8
+1 Это действительно сложный вопрос.
AraK
1
Для первых трех опубликованных ответов рассмотрите этот пример: struct S { template <class R = int> R get_me_R() { return R(); } };параметр шаблона не может быть выведен из контекста.
AraK
3
Хороший вопрос. 3 человека уже ответили, что это "не имеет смысла", и все они в целом не правы. Параметры шаблона функции не всегда вычитаются из параметров вызова функции. Например, если бы им было позволено, я мог бы написать template <int N = 1> int &increment(int &i) { i += N; return i; }, а затем increment(i);или increment<2>(i);. Как это, я должен написать increment<1>(i);.
Стив Джессоп
На самом деле, мой пример и примеры с AraK могут быть перегружены. Литб не может, я думаю, потому что параметр шаблона может быть выведен или может быть указан.
Стив Джессоп
3
@Steve: пропущенная точка с запятой - фактически новая перегрузка оператора EOL, дополняющая «Перегрузку пробелами C ++» Б. Ставтрупа, опубликованную в Журнале объектно-ориентированного программирования, 1 апреля 1992 г. ( www2.research.att.com/~bs/ paper.html )

Ответы:

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 ++. Смотрите этот отчет о дефектах Бьярна Страуструпа: Аргументы шаблона по умолчанию для шаблонов функций и что он говорит

Запрет стандартных шаблонных аргументов для шаблонов функций является ошибочным пережитком времени, когда автономные функции рассматривались как граждане второго сорта и требовали, чтобы все аргументы шаблона выводились из аргументов функции, а не указывались.

Ограничение серьезно ограничивает стиль программирования, излишне выделяя автономные функции из функций-членов, что затрудняет написание кода в стиле STL.

Йоханнес Шауб - Литб
источник
@Arman, ссылка на отчет о дефектах содержит изменения, внесенные в рабочий проект для C ++ 0x и обсуждения. Аргументы, которые не выводятся и не указываются явно, получаются из аргументов по умолчанию. GCC4.4 поддерживает стандартные аргументы для шаблонов функций в режиме C ++ 0x.
Йоханнес Шауб -
4
Ничего общего с вопросом или ответом, но Херб Саттер назвал новый стандарт C ++ 11 после последней субботней встречи. Я только что прочитал это сегодня и чувствую, что делюсь :) herbutter.wordpress.com/2010/03/13/…
Дэвид Родригес - dribeas
и обязательный последующий вопрос ... когда ожидается, что он попадет в другие компиляторы :)
Джейми Кук
@ JohannesSchaub-litb У меня была та же проблема: нет возможности указать тип по умолчанию в функции шаблона. Я решил с помощью явной реализации функции на тип по умолчанию ( doubleв моем случае). Возможно, это не «общее», но есть ли недостаток в этой практике? Спасибо.
JackOLantern
Следующий код не может быть скомпилирован с ошибками, такими как error: invalid conversion from ‘int’ to ‘int*’любая идея, почему: `#include <массив> #include <алгоритм> #include <функционал> шаблон <имя-итератора typename Comp = std :: less <итератор>> 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
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); }

LITB:

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, а закомментированный вызов не может быть скомпилирован должным образом.

Поэтому я подозреваю, что параметры шаблона по умолчанию «не нужны», но, вероятно, только в том смысле, в котором аргументы функций по умолчанию «не нужны». Как указывается в отчете о дефектах Страуструпа, добавление необработанных параметров было слишком поздно для того, чтобы кто-либо мог понять и / или действительно оценить, что он сделал полезными значения по умолчанию. Таким образом, текущая ситуация основана на версии шаблонов функций, которая никогда не была стандартной.

Стив Джессоп
источник
@Steve: То есть яйцо было быстрее курицы? :) Интересно. Спасибо.
Арман
1
Вероятно, только одна из тех вещей. Процесс стандартизации C ++ отчасти идет медленно, поэтому у людей есть время понять, когда изменение создает возможности или трудности в других местах стандарта. Надеемся, что трудности поймут люди, внедряющие проект стандарта, когда они обнаружат противоречие или двусмысленность. Возможности разрешать вещи, которые раньше были запрещены, полагаться на того, кто хочет написать код, отметив, что он больше не должен быть незаконным ...
Стив Джессоп
2
Еще один для вас: template<typename T = void> int SomeFunction();. Параметр шаблона здесь никогда не используется, и фактически функция никогда не вызывается; единственное место, на которое он ссылается, находится в decltypeили sizeof. Имя намеренно совпадает с именем другой функции, но тот факт, что это шаблон, означает, что компилятор предпочтет свободную функцию, если она существует. Они используются в SFINAE для обеспечения поведения по умолчанию, когда определение функции отсутствует.
Том
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
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();

Как видим, нет необходимости явно устанавливать второй параметр. Может быть, это будет кому-то полезно.

alariq
источник
Это определенно не ответ.
Щенок
@deadmg - можешь объяснить почему? Мы не все гуру шаблонов C ++. Спасибо.
Кев
Это обходной путь, который довольно опрятен, но не охватывает все случаи, которые вы можете захотеть. Например, как бы вы применили это к конструктору?
Тибериу Савин
@TiberiuSavin - если я вас правильно понял, то вы можете сделать так: template <typename E, typename ARR_E> worker <E, ARR_E> :: worker (ARR_E * parr) {parr_ = parr; }. А затем используйте его следующим образом: worker <int> w2 (& my_array);
аларик